Merge branch 'NDK-Refactor' into NDK

Conflicts:
	examples/HardwareInfo/main.cpp
	include/Nazara/Renderer/Enums.hpp
	include/Nazara/Renderer/GpuQuery.hpp
	include/Nazara/Renderer/OpenGL.hpp
	include/Nazara/Renderer/RenderBuffer.hpp
	include/Nazara/Renderer/RenderTexture.hpp
	include/Nazara/Renderer/Texture.hpp
	src/Nazara/Graphics/AbstractRenderTechnique.cpp
	src/Nazara/Graphics/DeferredRenderTechnique.cpp
	src/Nazara/Graphics/Material.cpp
	src/Nazara/Graphics/SkyboxBackground.cpp
	src/Nazara/Renderer/GpuQuery.cpp
	src/Nazara/Renderer/OpenGL.cpp
	src/Nazara/Renderer/RenderBuffer.cpp
	src/Nazara/Renderer/RenderTexture.cpp
	src/Nazara/Renderer/Renderer.cpp
	src/Nazara/Renderer/Shader.cpp
	src/Nazara/Renderer/ShaderStage.cpp
	src/Nazara/Renderer/Texture.cpp

Former-commit-id: 2f1c7e9f9766f59ab83d9405856a1898ac4ab48f
This commit is contained in:
Lynix
2015-09-25 23:16:58 +02:00
613 changed files with 68051 additions and 66125 deletions

View File

@@ -7,12 +7,14 @@
#ifndef NAZARA_LOADERS_DDS_CONSTANTS_HPP
#define NAZARA_LOADERS_DDS_CONSTANTS_HPP
#include <Nazara/Prerequesites.hpp>
#define DDS_MAGIC 0x20534444
#define DDS_DXT1 0x31545844
#define DDS_DXT3 0x33545844
#define DDS_DXT5 0x35545844
inline constexpr nzUInt32 FourCC(nzUInt32 a, nzUInt32 b, nzUInt32 c, nzUInt32 d)
inline constexpr Nz::UInt32 FourCC(Nz::UInt32 a, Nz::UInt32 b, Nz::UInt32 c, Nz::UInt32 d)
{
return a << 0 |
b << 8 |
@@ -20,7 +22,7 @@ inline constexpr nzUInt32 FourCC(nzUInt32 a, nzUInt32 b, nzUInt32 c, nzUInt32 d)
d << 24;
}
enum D3D10_RESOURCE_DIMENSION : nzUInt32
enum D3D10_RESOURCE_DIMENSION : Nz::UInt32
{
D3D10_RESOURCE_DIMENSION_UNKNOWN = 0,
D3D10_RESOURCE_DIMENSION_BUFFER = 1,
@@ -38,7 +40,7 @@ enum D3D10_RESOURCE_MISC
D3D10_RESOURCE_MISC_GDI_COMPATIBLE = 0x20L
};
enum D3DFMT : nzUInt32
enum D3DFMT : Nz::UInt32
{
D3DFMT_UNKNOWN = 0,
@@ -222,7 +224,7 @@ enum DDS_SAVE
DDS_SAVE_MAX
};
enum DXGI_FORMAT : nzUInt32
enum DXGI_FORMAT : Nz::UInt32
{
DXGI_FORMAT_UNKNOWN = 0,
DXGI_FORMAT_R32G32B32A32_TYPELESS = 1,
@@ -328,47 +330,47 @@ enum DXGI_FORMAT : nzUInt32
struct DDSPixelFormat // DDPIXELFORMAT
{
nzUInt32 size;
nzUInt32 flags;
nzUInt32 fourCC;
nzUInt32 bpp;
nzUInt32 redMask;
nzUInt32 greenMask;
nzUInt32 blueMask;
nzUInt32 alphaMask;
Nz::UInt32 size;
Nz::UInt32 flags;
Nz::UInt32 fourCC;
Nz::UInt32 bpp;
Nz::UInt32 redMask;
Nz::UInt32 greenMask;
Nz::UInt32 blueMask;
Nz::UInt32 alphaMask;
};
static_assert(sizeof(DDSPixelFormat) == 8*sizeof(nzUInt32), "DDSPixelFormat must be packed");
static_assert(sizeof(DDSPixelFormat) == 8*sizeof(Nz::UInt32), "DDSPixelFormat must be packed");
struct DDSHeader
{
nzUInt32 size;
nzUInt32 flags;
nzUInt32 height;
nzUInt32 width;
nzUInt32 pitch;
nzUInt32 depth;
nzUInt32 levelCount;
nzUInt32 reserved1[11];
Nz::UInt32 size;
Nz::UInt32 flags;
Nz::UInt32 height;
Nz::UInt32 width;
Nz::UInt32 pitch;
Nz::UInt32 depth;
Nz::UInt32 levelCount;
Nz::UInt32 reserved1[11];
DDSPixelFormat format;
nzUInt32 ddsCaps1;
nzUInt32 ddsCaps2;
nzUInt32 ddsCaps3;
nzUInt32 ddsCaps4;
nzUInt32 reserved2;
Nz::UInt32 ddsCaps1;
Nz::UInt32 ddsCaps2;
Nz::UInt32 ddsCaps3;
Nz::UInt32 ddsCaps4;
Nz::UInt32 reserved2;
};
static_assert(sizeof(DDSHeader) == 23*sizeof(nzUInt32) + sizeof(DDSPixelFormat), "DDSHeader must be packed");
static_assert(sizeof(DDSHeader) == 23*sizeof(Nz::UInt32) + sizeof(DDSPixelFormat), "DDSHeader must be packed");
struct DDSHeaderDX10Ext
{
DXGI_FORMAT dxgiFormat;
D3D10_RESOURCE_DIMENSION resourceDimension;
nzUInt32 miscFlag;
nzUInt32 arraySize;
nzUInt32 reserved;
Nz::UInt32 miscFlag;
Nz::UInt32 arraySize;
Nz::UInt32 reserved;
};
static_assert(sizeof(DDSHeaderDX10Ext) == 5*sizeof(nzUInt32), "DDSHeaderDX10Ext must be packed");
static_assert(sizeof(DDSHeaderDX10Ext) == 5*sizeof(Nz::UInt32), "DDSHeaderDX10Ext must be packed");
#endif // NAZARA_LOADERS_DDS_CONSTANTS_HPP

View File

@@ -11,113 +11,119 @@
#include <memory>
#include <Nazara/Utility/Debug.hpp>
namespace
namespace Nz
{
bool IsSupported(const NzString& extension)
namespace
{
return (extension == "dds");
}
nzTernary Check(NzInputStream& stream, const NzImageParams& parameters)
{
NazaraUnused(parameters);
nzUInt32 magic;
if (stream.Read(&magic, sizeof(nzUInt32)) == sizeof(nzUInt32))
bool IsSupported(const String& extension)
{
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(&magic, sizeof(nzUInt32));
#endif
if (magic == DDS_MAGIC)
return nzTernary_True;
return (extension == "dds");
}
return nzTernary_False;
}
bool Load(NzImage* image, NzInputStream& stream, const NzImageParams& parameters)
{
NazaraUnused(parameters);
DDSHeader header;
if (stream.Read(&header, sizeof(DDSHeader)) != sizeof(DDSHeader))
Ternary Check(InputStream& stream, const ImageParams& parameters)
{
NazaraError("Failed to read DDS header");
return false;
}
NazaraUnused(parameters);
DDSHeaderDX10Ext headerDX10;
if (header.format.flags & DDPF_FOURCC && header.format.fourCC == D3DFMT_DX10)
{
if (stream.Read(&headerDX10, sizeof(DDSHeaderDX10Ext)) != sizeof(DDSHeaderDX10Ext))
UInt32 magic;
if (stream.Read(&magic, sizeof(UInt32)) == sizeof(UInt32))
{
NazaraError("Failed to read DDS DX10 extension header");
#ifdef NAZARA_BIG_ENDIAN
ByteSwap(&magic, sizeof(UInt32));
#endif
if (magic == DDS_MAGIC)
return Ternary_True;
}
return Ternary_False;
}
bool Load(Image* image, InputStream& stream, const ImageParams& parameters)
{
NazaraUnused(parameters);
DDSHeader header;
if (stream.Read(&header, sizeof(DDSHeader)) != sizeof(DDSHeader))
{
NazaraError("Failed to read DDS header");
return false;
}
DDSHeaderDX10Ext headerDX10;
if (header.format.flags & DDPF_FOURCC && header.format.fourCC == D3DFMT_DX10)
{
if (stream.Read(&headerDX10, sizeof(DDSHeaderDX10Ext)) != sizeof(DDSHeaderDX10Ext))
{
NazaraError("Failed to read DDS DX10 extension header");
return false;
}
}
else
{
headerDX10.arraySize = 1;
headerDX10.dxgiFormat = DXGI_FORMAT_UNKNOWN;
headerDX10.miscFlag = 0;
headerDX10.resourceDimension = D3D10_RESOURCE_DIMENSION_UNKNOWN;
}
#ifdef NAZARA_BIG_ENDIAN
// Les fichiers DDS sont en little endian
ByteSwap(&header.size, sizeof(UInt32));
ByteSwap(&header.flags, sizeof(UInt32));
ByteSwap(&header.height, sizeof(UInt32));
ByteSwap(&header.width, sizeof(UInt32));
ByteSwap(&header.pitch, sizeof(UInt32));
ByteSwap(&header.depth, sizeof(UInt32));
ByteSwap(&header.levelCount, sizeof(UInt32));
// DDS_PixelFormat
ByteSwap(&header.format.size, sizeof(UInt32));
ByteSwap(&header.format.flags, sizeof(UInt32));
ByteSwap(&header.format.fourCC, sizeof(UInt32));
ByteSwap(&header.format.bpp, sizeof(UInt32));
ByteSwap(&header.format.redMask, sizeof(UInt32));
ByteSwap(&header.format.greenMask, sizeof(UInt32));
ByteSwap(&header.format.blueMask, sizeof(UInt32));
ByteSwap(&header.format.alphaMask, sizeof(UInt32));
ByteSwap(&header.ddsCaps1, sizeof(UInt32));
ByteSwap(&header.ddsCaps2, sizeof(UInt32));
ByteSwap(&header.ddsCaps3, sizeof(UInt32));
ByteSwap(&header.ddsCaps4, sizeof(UInt32));
#endif
unsigned int width = header.width;
unsigned int height = header.height;
unsigned int depth = std::max(header.depth, 1U);
unsigned int levelCount = (parameters.levelCount > 0) ? std::min(parameters.levelCount, static_cast<UInt8>(header.levelCount)) : header.levelCount;
// Détermination du type
ImageType type;
if (header.ddsCaps2 & DDSCAPS2_CUBEMAP)
type = ImageType_Cubemap;
else if (header.ddsCaps2 & DDSCAPS2_VOLUME)
type = ImageType_3D;
// Détermination du format
PixelFormatType format;
if (parameters.loadFormat != PixelFormatType_Undefined)
image->Convert(parameters.loadFormat);
return true;
}
else
}
namespace Loaders
{
void RegisterDDS()
{
headerDX10.arraySize = 1;
headerDX10.dxgiFormat = DXGI_FORMAT_UNKNOWN;
headerDX10.miscFlag = 0;
headerDX10.resourceDimension = D3D10_RESOURCE_DIMENSION_UNKNOWN;
ImageLoader::RegisterLoader(IsSupported, Check, Load);
}
#ifdef NAZARA_BIG_ENDIAN
// Les fichiers DDS sont en little endian
NzByteSwap(&header.size, sizeof(nzUInt32));
NzByteSwap(&header.flags, sizeof(nzUInt32));
NzByteSwap(&header.height, sizeof(nzUInt32));
NzByteSwap(&header.width, sizeof(nzUInt32));
NzByteSwap(&header.pitch, sizeof(nzUInt32));
NzByteSwap(&header.depth, sizeof(nzUInt32));
NzByteSwap(&header.levelCount, sizeof(nzUInt32));
// DDS_PixelFormat
NzByteSwap(&header.format.size, sizeof(nzUInt32));
NzByteSwap(&header.format.flags, sizeof(nzUInt32));
NzByteSwap(&header.format.fourCC, sizeof(nzUInt32));
NzByteSwap(&header.format.bpp, sizeof(nzUInt32));
NzByteSwap(&header.format.redMask, sizeof(nzUInt32));
NzByteSwap(&header.format.greenMask, sizeof(nzUInt32));
NzByteSwap(&header.format.blueMask, sizeof(nzUInt32));
NzByteSwap(&header.format.alphaMask, sizeof(nzUInt32));
NzByteSwap(&header.ddsCaps1, sizeof(nzUInt32));
NzByteSwap(&header.ddsCaps2, sizeof(nzUInt32));
NzByteSwap(&header.ddsCaps3, sizeof(nzUInt32));
NzByteSwap(&header.ddsCaps4, sizeof(nzUInt32));
#endif
unsigned int width = header.width;
unsigned int height = header.height;
unsigned int depth = std::max(header.depth, 1U);
unsigned int levelCount = (parameters.levelCount > 0) ? std::min(parameters.levelCount, static_cast<nzUInt8>(header.levelCount)) : header.levelCount;
// Détermination du type
nzImageType type;
if (header.ddsCaps2 & DDSCAPS2_CUBEMAP)
type = nzImageType_Cubemap;
else if (header.ddsCaps2 & DDSCAPS2_VOLUME)
type = nzImageType_3D;
// Détermination du format
nzPixelFormat format;
if (parameters.loadFormat != nzPixelFormat_Undefined)
image->Convert(parameters.loadFormat);
return true;
void UnregisterDDS()
{
ImageLoader::UnregisterLoader(IsSupported, Check, Load);
}
}
}
void NzLoaders_DDS_Register()
{
NzImageLoader::RegisterLoader(IsSupported, Check, Load);
}
void NzLoaders_DDS_Unregister()
{
NzImageLoader::UnregisterLoader(IsSupported, Check, Load);
}

View File

@@ -9,7 +9,13 @@
#include <Nazara/Prerequesites.hpp>
void NzLoaders_DDS_Register();
void NzLoaders_DDS_Unregister();
namespace Nz
{
namespace Loaders
{
void RegisterDDS();
void UnregisterDDS();
}
}
#endif // NAZARA_LOADERS_DDS_HPP

View File

@@ -17,437 +17,443 @@
#include <set>
#include <Nazara/Utility/Debug.hpp>
namespace
namespace Nz
{
class FreeTypeLibrary;
FT_Library s_library;
std::shared_ptr<FreeTypeLibrary> s_libraryOwner;
float s_invScaleFactor = 1.f / (1 << 6); // 1/64
extern "C"
unsigned long FT_StreamRead(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count)
namespace
{
// http://www.freetype.org/freetype2/docs/reference/ft2-system_interface.html#FT_Stream_IoFunc
NzInputStream& inputStream = *static_cast<NzInputStream*>(stream->descriptor.pointer);
class FreeTypeLibrary;
// La valeur de count indique une opération de lecture ou de positionnement
if (count > 0)
FT_Library s_library;
std::shared_ptr<FreeTypeLibrary> s_libraryOwner;
float s_invScaleFactor = 1.f / (1 << 6); // 1/64
extern "C"
unsigned long FT_StreamRead(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count)
{
// Dans le premier cas, une erreur est symbolisée par un retour nul
if (inputStream.SetCursorPos(offset))
return static_cast<unsigned long>(inputStream.Read(buffer, count));
else
return 0;
}
else
{
// Dans le second cas, une erreur est symbolisée par un retour non-nul
if (inputStream.SetCursorPos(offset))
return 0;
else
return 42; // La réponse à la grande question
}
}
// http://www.freetype.org/freetype2/docs/reference/ft2-system_interface.html#FT_Stream_IoFunc
InputStream& inputStream = *static_cast<InputStream*>(stream->descriptor.pointer);
extern "C"
void FT_StreamClose(FT_Stream stream)
{
// http://www.freetype.org/freetype2/docs/reference/ft2-system_interface.html#FT_Stream_CloseFunc
// Les streams dans Nazara ne se ferment pas explicitement
NazaraUnused(stream);
}
class FreeTypeLibrary
{
// Cette classe ne sert qu'à être utilisée avec un std::shared_ptr
// pour ne libérer FreeType que lorsque plus personne ne l'utilise
public:
FreeTypeLibrary() = default;
~FreeTypeLibrary()
// La valeur de count indique une opération de lecture ou de positionnement
if (count > 0)
{
FT_Done_FreeType(s_library);
s_library = nullptr;
}
};
class FreeTypeStream : public NzFontData
{
public:
FreeTypeStream() :
m_face(nullptr),
m_library(s_libraryOwner),
m_characterSize(0)
{
}
~FreeTypeStream()
{
if (m_face)
FT_Done_Face(m_face);
}
bool Check()
{
// Test d'ouverture (http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Open_Face)
return FT_Open_Face(s_library, &m_args, -1, nullptr) == 0;
}
bool ExtractGlyph(unsigned int characterSize, char32_t character, nzUInt32 style, NzFontGlyph* dst) override
{
#ifdef NAZARA_DEBUG
if (!dst)
{
NazaraError("Glyph destination cannot be null");
return false;
}
#endif
SetCharacterSize(characterSize);
if (FT_Load_Char(m_face, character, FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL) != 0)
{
NazaraError("Failed to load character");
return false;
}
FT_GlyphSlot& glyph = m_face->glyph;
const FT_Pos boldStrength = 2 << 6;
bool embolden = (style & nzTextStyle_Bold);
dst->advance = (embolden) ? boldStrength >> 6 : 0;
if (embolden && glyph->format == FT_GLYPH_FORMAT_OUTLINE)
{
// http://www.freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_Outline_Embolden
FT_Outline_Embolden(&glyph->outline, boldStrength);
embolden = false;
}
// http://www.freetype.org/freetype2/docs/reference/ft2-glyph_management.html#FT_Glyph_To_Bitmap
// Conversion du glyphe vers le format bitmap
// Cette fonction ne fait rien dans le cas où le glyphe est déjà un bitmap
if (FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL) != 0)
{
NazaraError("Failed to convert glyph to bitmap");
return false;
}
// Dans le cas où nous voulons des caractères gras mais que nous n'avons pas pu agir plus tôt
// nous demandons à FreeType d'agir directement sur le bitmap généré
if (embolden)
{
// http://www.freetype.org/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden
// "If you want to embolden the bitmap owned by a FT_GlyphSlot_Rec, you should call FT_GlyphSlot_Own_Bitmap on the slot first"
FT_GlyphSlot_Own_Bitmap(glyph);
FT_Bitmap_Embolden(s_library, &glyph->bitmap, boldStrength, boldStrength);
}
dst->advance += glyph->metrics.horiAdvance >> 6;
dst->aabb.x = glyph->metrics.horiBearingX >> 6;
dst->aabb.y = -(glyph->metrics.horiBearingY >> 6); // Inversion du repère
dst->aabb.width = glyph->metrics.width >> 6;
dst->aabb.height = glyph->metrics.height >> 6;
unsigned int width = glyph->bitmap.width;
unsigned int height = glyph->bitmap.rows;
if (width > 0 && height > 0)
{
dst->image.Create(nzImageType_2D, nzPixelFormat_A8, width, height);
nzUInt8* pixels = dst->image.GetPixels();
const nzUInt8* data = glyph->bitmap.buffer;
// Selon la documentation FreeType, le glyphe peut être encodé en format A8 (huit bits d'alpha par pixel)
// ou au format A1 (un bit d'alpha par pixel).
// Cependant dans un cas comme dans l'autre, il nous faut gérer le pitch (les données peuvent ne pas être contigues)
// ainsi que le padding dans le cas du format A1 (Chaque ligne prends un nombre fixe d'octets)
if (glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
{
// Format A1
for (unsigned int y = 0; y < height; ++y)
{
for (unsigned int x = 0; x < width; ++x)
*pixels++ = (data[x/8] & ((1 << (7 - x%8)) ? 255 : 0));
data += glyph->bitmap.pitch;
}
}
else
{
// Format A8
if (glyph->bitmap.pitch == static_cast<int>(width*sizeof(nzUInt8))) // Pouvons-nous copier directement ?
dst->image.Update(glyph->bitmap.buffer);
else
{
for (unsigned int y = 0; y < height; ++y)
{
std::memcpy(pixels, data, width*sizeof(nzUInt8));
data += glyph->bitmap.pitch;
pixels += width*sizeof(nzUInt8);
}
}
}
}
else
dst->image.Destroy(); // On s'assure que l'image ne contient alors rien
return true;
}
NzString GetFamilyName() const override
{
return m_face->family_name;
}
NzString GetStyleName() const override
{
return m_face->style_name;
}
bool HasKerning() const override
{
return FT_HAS_KERNING(m_face) != 0;
}
bool IsScalable() const override
{
return FT_IS_SCALABLE(m_face) != 0;
}
bool Open()
{
return FT_Open_Face(s_library, &m_args, 0, &m_face) == 0;
}
int QueryKerning(unsigned int characterSize, char32_t first, char32_t second) const override
{
if (FT_HAS_KERNING(m_face))
{
SetCharacterSize(characterSize);
FT_Vector kerning;
FT_Get_Kerning(m_face, FT_Get_Char_Index(m_face, first), FT_Get_Char_Index(m_face, second), FT_KERNING_DEFAULT, &kerning);
if (!FT_IS_SCALABLE(m_face))
return kerning.x; // Taille déjà précisée en pixels dans ce cas
return kerning.x >> 6;
}
// Dans le premier cas, une erreur est symbolisée par un retour nul
if (inputStream.SetCursorPos(offset))
return static_cast<unsigned long>(inputStream.Read(buffer, count));
else
return 0;
}
unsigned int QueryLineHeight(unsigned int characterSize) const override
else
{
SetCharacterSize(characterSize);
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Size_Metrics
return m_face->size->metrics.height >> 6;
}
float QueryUnderlinePosition(unsigned int characterSize) const override
{
if (FT_IS_SCALABLE(m_face))
{
SetCharacterSize(characterSize);
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
return static_cast<float>(FT_MulFix(m_face->underline_position, m_face->size->metrics.y_scale)) * s_invScaleFactor;
}
// Dans le second cas, une erreur est symbolisée par un retour non-nul
if (inputStream.SetCursorPos(offset))
return 0;
else
return characterSize / 10.f; // Joker ?
return 42; // La réponse à la grande question
}
}
float QueryUnderlineThickness(unsigned int characterSize) const override
{
if (FT_IS_SCALABLE(m_face))
extern "C"
void FT_StreamClose(FT_Stream stream)
{
// http://www.freetype.org/freetype2/docs/reference/ft2-system_interface.html#FT_Stream_CloseFunc
// Les streams dans Nazara ne se ferment pas explicitement
NazaraUnused(stream);
}
class FreeTypeLibrary
{
// Cette classe ne sert qu'à être utilisée avec un std::shared_ptr
// pour ne libérer FreeType que lorsque plus personne ne l'utilise
public:
FreeTypeLibrary() = default;
~FreeTypeLibrary()
{
SetCharacterSize(characterSize);
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
return static_cast<float>(FT_MulFix(m_face->underline_thickness, m_face->size->metrics.y_scale)) * s_invScaleFactor;
FT_Done_FreeType(s_library);
s_library = nullptr;
}
else
return characterSize/15.f; // Joker ?
}
bool SetFile(const NzString& filePath)
{
std::unique_ptr<NzFile> file(new NzFile);
if (!file->Open(filePath, nzOpenMode_ReadOnly))
{
NazaraError("Failed to open stream from file: " + NzError::GetLastError());
return false;
}
m_ownedStream = std::move(file);
SetStream(*m_ownedStream);
return true;
}
void SetMemory(const void* data, std::size_t size)
{
m_ownedStream.reset(new NzMemoryStream(data, size));
SetStream(*m_ownedStream);
}
void SetStream(NzInputStream& stream)
{
m_stream.base = nullptr;
m_stream.close = FT_StreamClose;
m_stream.descriptor.pointer = &stream;
m_stream.read = FT_StreamRead;
m_stream.pos = 0;
m_stream.size = static_cast<unsigned long>(stream.GetSize());
m_args.driver = 0;
m_args.flags = FT_OPEN_STREAM;
m_args.stream = &m_stream;
}
bool SupportsStyle(nzUInt32 style) const override
{
///TODO
return style == nzTextStyle_Regular || style == nzTextStyle_Bold;
}
private:
void SetCharacterSize(unsigned int characterSize) const
{
if (m_characterSize != characterSize)
{
FT_Set_Pixel_Sizes(m_face, 0, characterSize);
m_characterSize = characterSize;
}
}
FT_Open_Args m_args;
FT_Face m_face;
FT_StreamRec m_stream;
std::shared_ptr<FreeTypeLibrary> m_library;
std::unique_ptr<NzInputStream> m_ownedStream;
mutable unsigned int m_characterSize;
};
bool IsSupported(const NzString& extension)
{
///FIXME: Je suppose qu'il en manque quelques unes..
static std::set<NzString> supportedExtensions = {
"afm", "bdf", "cff", "cid", "dfont", "fnt", "fon", "otf", "pfa", "pfb", "pfm", "pfr", "sfnt", "ttc", "tte", "ttf"
};
return supportedExtensions.find(extension) != supportedExtensions.end();
class FreeTypeStream : public FontData
{
public:
FreeTypeStream() :
m_face(nullptr),
m_library(s_libraryOwner),
m_characterSize(0)
{
}
~FreeTypeStream()
{
if (m_face)
FT_Done_Face(m_face);
}
bool Check()
{
// Test d'ouverture (http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Open_Face)
return FT_Open_Face(s_library, &m_args, -1, nullptr) == 0;
}
bool ExtractGlyph(unsigned int characterSize, char32_t character, UInt32 style, FontGlyph* dst) override
{
#ifdef NAZARA_DEBUG
if (!dst)
{
NazaraError("Glyph destination cannot be null");
return false;
}
#endif
SetCharacterSize(characterSize);
if (FT_Load_Char(m_face, character, FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL) != 0)
{
NazaraError("Failed to load character");
return false;
}
FT_GlyphSlot& glyph = m_face->glyph;
const FT_Pos boldStrength = 2 << 6;
bool embolden = (style & TextStyle_Bold);
dst->advance = (embolden) ? boldStrength >> 6 : 0;
if (embolden && glyph->format == FT_GLYPH_FORMAT_OUTLINE)
{
// http://www.freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_Outline_Embolden
FT_Outline_Embolden(&glyph->outline, boldStrength);
embolden = false;
}
// http://www.freetype.org/freetype2/docs/reference/ft2-glyph_management.html#FT_Glyph_To_Bitmap
// Conversion du glyphe vers le format bitmap
// Cette fonction ne fait rien dans le cas où le glyphe est déjà un bitmap
if (FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL) != 0)
{
NazaraError("Failed to convert glyph to bitmap");
return false;
}
// Dans le cas où nous voulons des caractères gras mais que nous n'avons pas pu agir plus tôt
// nous demandons à FreeType d'agir directement sur le bitmap généré
if (embolden)
{
// http://www.freetype.org/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden
// "If you want to embolden the bitmap owned by a FT_GlyphSlot_Rec, you should call FT_GlyphSlot_Own_Bitmap on the slot first"
FT_GlyphSlot_Own_Bitmap(glyph);
FT_Bitmap_Embolden(s_library, &glyph->bitmap, boldStrength, boldStrength);
}
dst->advance += glyph->metrics.horiAdvance >> 6;
dst->aabb.x = glyph->metrics.horiBearingX >> 6;
dst->aabb.y = -(glyph->metrics.horiBearingY >> 6); // Inversion du repère
dst->aabb.width = glyph->metrics.width >> 6;
dst->aabb.height = glyph->metrics.height >> 6;
unsigned int width = glyph->bitmap.width;
unsigned int height = glyph->bitmap.rows;
if (width > 0 && height > 0)
{
dst->image.Create(ImageType_2D, PixelFormatType_A8, width, height);
UInt8* pixels = dst->image.GetPixels();
const UInt8* data = glyph->bitmap.buffer;
// Selon la documentation FreeType, le glyphe peut être encodé en format A8 (huit bits d'alpha par pixel)
// ou au format A1 (un bit d'alpha par pixel).
// Cependant dans un cas comme dans l'autre, il nous faut gérer le pitch (les données peuvent ne pas être contigues)
// ainsi que le padding dans le cas du format A1 (Chaque ligne prends un nombre fixe d'octets)
if (glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
{
// Format A1
for (unsigned int y = 0; y < height; ++y)
{
for (unsigned int x = 0; x < width; ++x)
*pixels++ = (data[x/8] & ((1 << (7 - x%8)) ? 255 : 0));
data += glyph->bitmap.pitch;
}
}
else
{
// Format A8
if (glyph->bitmap.pitch == static_cast<int>(width*sizeof(UInt8))) // Pouvons-nous copier directement ?
dst->image.Update(glyph->bitmap.buffer);
else
{
for (unsigned int y = 0; y < height; ++y)
{
std::memcpy(pixels, data, width*sizeof(UInt8));
data += glyph->bitmap.pitch;
pixels += width*sizeof(UInt8);
}
}
}
}
else
dst->image.Destroy(); // On s'assure que l'image ne contient alors rien
return true;
}
String GetFamilyName() const override
{
return m_face->family_name;
}
String GetStyleName() const override
{
return m_face->style_name;
}
bool HasKerning() const override
{
return FT_HAS_KERNING(m_face) != 0;
}
bool IsScalable() const override
{
return FT_IS_SCALABLE(m_face) != 0;
}
bool Open()
{
return FT_Open_Face(s_library, &m_args, 0, &m_face) == 0;
}
int QueryKerning(unsigned int characterSize, char32_t first, char32_t second) const override
{
if (FT_HAS_KERNING(m_face))
{
SetCharacterSize(characterSize);
FT_Vector kerning;
FT_Get_Kerning(m_face, FT_Get_Char_Index(m_face, first), FT_Get_Char_Index(m_face, second), FT_KERNING_DEFAULT, &kerning);
if (!FT_IS_SCALABLE(m_face))
return kerning.x; // Taille déjà précisée en pixels dans ce cas
return kerning.x >> 6;
}
else
return 0;
}
unsigned int QueryLineHeight(unsigned int characterSize) const override
{
SetCharacterSize(characterSize);
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Size_Metrics
return m_face->size->metrics.height >> 6;
}
float QueryUnderlinePosition(unsigned int characterSize) const override
{
if (FT_IS_SCALABLE(m_face))
{
SetCharacterSize(characterSize);
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
return static_cast<float>(FT_MulFix(m_face->underline_position, m_face->size->metrics.y_scale)) * s_invScaleFactor;
}
else
return characterSize / 10.f; // Joker ?
}
float QueryUnderlineThickness(unsigned int characterSize) const override
{
if (FT_IS_SCALABLE(m_face))
{
SetCharacterSize(characterSize);
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
return static_cast<float>(FT_MulFix(m_face->underline_thickness, m_face->size->metrics.y_scale)) * s_invScaleFactor;
}
else
return characterSize/15.f; // Joker ?
}
bool SetFile(const String& filePath)
{
std::unique_ptr<File> file(new File);
if (!file->Open(filePath, OpenMode_ReadOnly))
{
NazaraError("Failed to open stream from file: " + Error::GetLastError());
return false;
}
m_ownedStream = std::move(file);
SetStream(*m_ownedStream);
return true;
}
void SetMemory(const void* data, std::size_t size)
{
m_ownedStream.reset(new MemoryStream(data, size));
SetStream(*m_ownedStream);
}
void SetStream(InputStream& stream)
{
m_stream.base = nullptr;
m_stream.close = FT_StreamClose;
m_stream.descriptor.pointer = &stream;
m_stream.read = FT_StreamRead;
m_stream.pos = 0;
m_stream.size = static_cast<unsigned long>(stream.GetSize());
m_args.driver = 0;
m_args.flags = FT_OPEN_STREAM;
m_args.stream = &m_stream;
}
bool SupportsStyle(UInt32 style) const override
{
///TODO
return style == TextStyle_Regular || style == TextStyle_Bold;
}
private:
void SetCharacterSize(unsigned int characterSize) const
{
if (m_characterSize != characterSize)
{
FT_Set_Pixel_Sizes(m_face, 0, characterSize);
m_characterSize = characterSize;
}
}
FT_Open_Args m_args;
FT_Face m_face;
FT_StreamRec m_stream;
std::shared_ptr<FreeTypeLibrary> m_library;
std::unique_ptr<InputStream> m_ownedStream;
mutable unsigned int m_characterSize;
};
bool IsSupported(const String& extension)
{
///FIXME: Je suppose qu'il en manque quelques unes..
static std::set<String> supportedExtensions = {
"afm", "bdf", "cff", "cid", "dfont", "fnt", "fon", "otf", "pfa", "pfb", "pfm", "pfr", "sfnt", "ttc", "tte", "ttf"
};
return supportedExtensions.find(extension) != supportedExtensions.end();
}
Ternary Check(InputStream& stream, const FontParams& parameters)
{
NazaraUnused(parameters);
FreeTypeStream face;
face.SetStream(stream);
if (face.Check())
return Ternary_True;
else
return Ternary_False;
}
bool LoadFile(Font* font, const String& filePath, const FontParams& parameters)
{
NazaraUnused(parameters);
std::unique_ptr<FreeTypeStream> face(new FreeTypeStream);
if (!face->SetFile(filePath))
{
NazaraError("Failed to open file");
return false;
}
if (!face->Open())
{
NazaraError("Failed to open face");
return false;
}
if (font->Create(face.get()))
{
face.release();
return true;
}
else
return false;
}
bool LoadMemory(Font* font, const void* data, std::size_t size, const FontParams& parameters)
{
NazaraUnused(parameters);
std::unique_ptr<FreeTypeStream> face(new FreeTypeStream);
face->SetMemory(data, size);
if (!face->Open())
{
NazaraError("Failed to open face");
return false;
}
if (font->Create(face.get()))
{
face.release();
return true;
}
else
return false;
}
bool LoadStream(Font* font, InputStream& stream, const FontParams& parameters)
{
NazaraUnused(parameters);
std::unique_ptr<FreeTypeStream> face(new FreeTypeStream);
face->SetStream(stream);
if (!face->Open())
{
NazaraError("Failed to open face");
return false;
}
if (font->Create(face.get()))
{
face.release();
return true;
}
else
return false;
}
}
nzTernary Check(NzInputStream& stream, const NzFontParams& parameters)
namespace Loaders
{
NazaraUnused(parameters);
FreeTypeStream face;
face.SetStream(stream);
if (face.Check())
return nzTernary_True;
else
return nzTernary_False;
}
bool LoadFile(NzFont* font, const NzString& filePath, const NzFontParams& parameters)
{
NazaraUnused(parameters);
std::unique_ptr<FreeTypeStream> face(new FreeTypeStream);
if (!face->SetFile(filePath))
void RegisterFreeType()
{
NazaraError("Failed to open file");
return false;
if (FT_Init_FreeType(&s_library) == 0)
{
s_libraryOwner.reset(new FreeTypeLibrary);
FontLoader::RegisterLoader(IsSupported, Check, LoadStream, LoadFile, LoadMemory);
}
else
{
s_library = nullptr; // On s'assure que le pointeur ne pointe pas sur n'importe quoi
NazaraWarning("Failed to initialize FreeType library");
}
}
if (!face->Open())
void UnregisterFreeType()
{
NazaraError("Failed to open face");
return false;
if (s_library)
{
FontLoader::UnregisterLoader(IsSupported, Check, LoadStream, LoadFile, LoadMemory);
s_libraryOwner.reset();
}
}
if (font->Create(face.get()))
{
face.release();
return true;
}
else
return false;
}
bool LoadMemory(NzFont* font, const void* data, std::size_t size, const NzFontParams& parameters)
{
NazaraUnused(parameters);
std::unique_ptr<FreeTypeStream> face(new FreeTypeStream);
face->SetMemory(data, size);
if (!face->Open())
{
NazaraError("Failed to open face");
return false;
}
if (font->Create(face.get()))
{
face.release();
return true;
}
else
return false;
}
bool LoadStream(NzFont* font, NzInputStream& stream, const NzFontParams& parameters)
{
NazaraUnused(parameters);
std::unique_ptr<FreeTypeStream> face(new FreeTypeStream);
face->SetStream(stream);
if (!face->Open())
{
NazaraError("Failed to open face");
return false;
}
if (font->Create(face.get()))
{
face.release();
return true;
}
else
return false;
}
}
void NzLoaders_FreeType_Register()
{
if (FT_Init_FreeType(&s_library) == 0)
{
s_libraryOwner.reset(new FreeTypeLibrary);
NzFontLoader::RegisterLoader(IsSupported, Check, LoadStream, LoadFile, LoadMemory);
}
else
{
s_library = nullptr; // On s'assure que le pointeur ne pointe pas sur n'importe quoi
NazaraWarning("Failed to initialize FreeType library");
}
}
void NzLoaders_FreeType_Unregister()
{
if (s_library)
{
NzFontLoader::UnregisterLoader(IsSupported, Check, LoadStream, LoadFile, LoadMemory);
s_libraryOwner.reset();
}
}

View File

@@ -9,7 +9,13 @@
#include <Nazara/Prerequesites.hpp>
void NzLoaders_FreeType_Register();
void NzLoaders_FreeType_Unregister();
namespace Nz
{
namespace Loaders
{
void RegisterFreeType();
void UnregisterFreeType();
}
}
#endif // NAZARA_LOADERS_FREETYPE_HPP

View File

@@ -5,170 +5,173 @@
#include <Nazara/Utility/Formats/MD2Constants.hpp>
#include <Nazara/Utility/Debug.hpp>
const nzUInt32 md2Ident = 'I' + ('D'<<8) + ('P'<<16) + ('2'<<24);
const NzVector3f md2Normals[162] =
namespace Nz
{
NzVector3f(-0.525731f, 0.000000f, 0.850651f),
NzVector3f(-0.442863f, 0.238856f, 0.864188f),
NzVector3f(-0.295242f, 0.000000f, 0.955423f),
NzVector3f(-0.309017f, 0.500000f, 0.809017f),
NzVector3f(-0.162460f, 0.262866f, 0.951056f),
NzVector3f(0.000000f, 0.000000f, 1.000000f),
NzVector3f(0.000000f, 0.850651f, 0.525731f),
NzVector3f(-0.147621f, 0.716567f, 0.681718f),
NzVector3f(0.147621f, 0.716567f, 0.681718f),
NzVector3f(0.000000f, 0.525731f, 0.850651f),
NzVector3f(0.309017f, 0.500000f, 0.809017f),
NzVector3f(0.525731f, 0.000000f, 0.850651f),
NzVector3f(0.295242f, 0.000000f, 0.955423f),
NzVector3f(0.442863f, 0.238856f, 0.864188f),
NzVector3f(0.162460f, 0.262866f, 0.951056f),
NzVector3f(-0.681718f, 0.147621f, 0.716567f),
NzVector3f(-0.809017f, 0.309017f, 0.500000f),
NzVector3f(-0.587785f, 0.425325f, 0.688191f),
NzVector3f(-0.850651f, 0.525731f, 0.000000f),
NzVector3f(-0.864188f, 0.442863f, 0.238856f),
NzVector3f(-0.716567f, 0.681718f, 0.147621f),
NzVector3f(-0.688191f, 0.587785f, 0.425325f),
NzVector3f(-0.500000f, 0.809017f, 0.309017f),
NzVector3f(-0.238856f, 0.864188f, 0.442863f),
NzVector3f(-0.425325f, 0.688191f, 0.587785f),
NzVector3f(-0.716567f, 0.681718f, -0.147621f),
NzVector3f(-0.500000f, 0.809017f, -0.309017f),
NzVector3f(-0.525731f, 0.850651f, 0.000000f),
NzVector3f(0.000000f, 0.850651f, -0.525731f),
NzVector3f(-0.238856f, 0.864188f, -0.442863f),
NzVector3f(0.000000f, 0.955423f, -0.295242f),
NzVector3f(-0.262866f, 0.951056f, -0.162460f),
NzVector3f(0.000000f, 1.000000f, 0.000000f),
NzVector3f(0.000000f, 0.955423f, 0.295242f),
NzVector3f(-0.262866f, 0.951056f, 0.162460f),
NzVector3f(0.238856f, 0.864188f, 0.442863f),
NzVector3f(0.262866f, 0.951056f, 0.162460f),
NzVector3f(0.500000f, 0.809017f, 0.309017f),
NzVector3f(0.238856f, 0.864188f, -0.442863f),
NzVector3f(0.262866f, 0.951056f, -0.162460f),
NzVector3f(0.500000f, 0.809017f, -0.309017f),
NzVector3f(0.850651f, 0.525731f, 0.000000f),
NzVector3f(0.716567f, 0.681718f, 0.147621f),
NzVector3f(0.716567f, 0.681718f, -0.147621f),
NzVector3f(0.525731f, 0.850651f, 0.000000f),
NzVector3f(0.425325f, 0.688191f, 0.587785f),
NzVector3f(0.864188f, 0.442863f, 0.238856f),
NzVector3f(0.688191f, 0.587785f, 0.425325f),
NzVector3f(0.809017f, 0.309017f, 0.500000f),
NzVector3f(0.681718f, 0.147621f, 0.716567f),
NzVector3f(0.587785f, 0.425325f, 0.688191f),
NzVector3f(0.955423f, 0.295242f, 0.000000f),
NzVector3f(1.000000f, 0.000000f, 0.000000f),
NzVector3f(0.951056f, 0.162460f, 0.262866f),
NzVector3f(0.850651f, -0.525731f, 0.000000f),
NzVector3f(0.955423f, -0.295242f, 0.000000f),
NzVector3f(0.864188f, -0.442863f, 0.238856f),
NzVector3f(0.951056f, -0.162460f, 0.262866f),
NzVector3f(0.809017f, -0.309017f, 0.500000f),
NzVector3f(0.681718f, -0.147621f, 0.716567f),
NzVector3f(0.850651f, 0.000000f, 0.525731f),
NzVector3f(0.864188f, 0.442863f, -0.238856f),
NzVector3f(0.809017f, 0.309017f, -0.500000f),
NzVector3f(0.951056f, 0.162460f, -0.262866f),
NzVector3f(0.525731f, 0.000000f, -0.850651f),
NzVector3f(0.681718f, 0.147621f, -0.716567f),
NzVector3f(0.681718f, -0.147621f, -0.716567f),
NzVector3f(0.850651f, 0.000000f, -0.525731f),
NzVector3f(0.809017f, -0.309017f, -0.500000f),
NzVector3f(0.864188f, -0.442863f, -0.238856f),
NzVector3f(0.951056f, -0.162460f, -0.262866f),
NzVector3f(0.147621f, 0.716567f, -0.681718f),
NzVector3f(0.309017f, 0.500000f, -0.809017f),
NzVector3f(0.425325f, 0.688191f, -0.587785f),
NzVector3f(0.442863f, 0.238856f, -0.864188f),
NzVector3f(0.587785f, 0.425325f, -0.688191f),
NzVector3f(0.688191f, 0.587785f, -0.425325f),
NzVector3f(-0.147621f, 0.716567f, -0.681718f),
NzVector3f(-0.309017f, 0.500000f, -0.809017f),
NzVector3f(0.000000f, 0.525731f, -0.850651f),
NzVector3f(-0.525731f, 0.000000f, -0.850651f),
NzVector3f(-0.442863f, 0.238856f, -0.864188f),
NzVector3f(-0.295242f, 0.000000f, -0.955423f),
NzVector3f(-0.162460f, 0.262866f, -0.951056f),
NzVector3f(0.000000f, 0.000000f, -1.000000f),
NzVector3f(0.295242f, 0.000000f, -0.955423f),
NzVector3f(0.162460f, 0.262866f, -0.951056f),
NzVector3f(-0.442863f, -0.238856f, -0.864188f),
NzVector3f(-0.309017f, -0.500000f, -0.809017f),
NzVector3f(-0.162460f, -0.262866f, -0.951056f),
NzVector3f(0.000000f, -0.850651f, -0.525731f),
NzVector3f(-0.147621f, -0.716567f, -0.681718f),
NzVector3f(0.147621f, -0.716567f, -0.681718f),
NzVector3f(0.000000f, -0.525731f, -0.850651f),
NzVector3f(0.309017f, -0.500000f, -0.809017f),
NzVector3f(0.442863f, -0.238856f, -0.864188f),
NzVector3f(0.162460f, -0.262866f, -0.951056f),
NzVector3f(0.238856f, -0.864188f, -0.442863f),
NzVector3f(0.500000f, -0.809017f, -0.309017f),
NzVector3f(0.425325f, -0.688191f, -0.587785f),
NzVector3f(0.716567f, -0.681718f, -0.147621f),
NzVector3f(0.688191f, -0.587785f, -0.425325f),
NzVector3f(0.587785f, -0.425325f, -0.688191f),
NzVector3f(0.000000f, -0.955423f, -0.295242f),
NzVector3f(0.000000f, -1.000000f, 0.000000f),
NzVector3f(0.262866f, -0.951056f, -0.162460f),
NzVector3f(0.000000f, -0.850651f, 0.525731f),
NzVector3f(0.000000f, -0.955423f, 0.295242f),
NzVector3f(0.238856f, -0.864188f, 0.442863f),
NzVector3f(0.262866f, -0.951056f, 0.162460f),
NzVector3f(0.500000f, -0.809017f, 0.309017f),
NzVector3f(0.716567f, -0.681718f, 0.147621f),
NzVector3f(0.525731f, -0.850651f, 0.000000f),
NzVector3f(-0.238856f, -0.864188f, -0.442863f),
NzVector3f(-0.500000f, -0.809017f, -0.309017f),
NzVector3f(-0.262866f, -0.951056f, -0.162460f),
NzVector3f(-0.850651f, -0.525731f, 0.000000f),
NzVector3f(-0.716567f, -0.681718f, -0.147621f),
NzVector3f(-0.716567f, -0.681718f, 0.147621f),
NzVector3f(-0.525731f, -0.850651f, 0.000000f),
NzVector3f(-0.500000f, -0.809017f, 0.309017f),
NzVector3f(-0.238856f, -0.864188f, 0.442863f),
NzVector3f(-0.262866f, -0.951056f, 0.162460f),
NzVector3f(-0.864188f, -0.442863f, 0.238856f),
NzVector3f(-0.809017f, -0.309017f, 0.500000f),
NzVector3f(-0.688191f, -0.587785f, 0.425325f),
NzVector3f(-0.681718f, -0.147621f, 0.716567f),
NzVector3f(-0.442863f, -0.238856f, 0.864188f),
NzVector3f(-0.587785f, -0.425325f, 0.688191f),
NzVector3f(-0.309017f, -0.500000f, 0.809017f),
NzVector3f(-0.147621f, -0.716567f, 0.681718f),
NzVector3f(-0.425325f, -0.688191f, 0.587785f),
NzVector3f(-0.162460f, -0.262866f, 0.951056f),
NzVector3f(0.442863f, -0.238856f, 0.864188f),
NzVector3f(0.162460f, -0.262866f, 0.951056f),
NzVector3f(0.309017f, -0.500000f, 0.809017f),
NzVector3f(0.147621f, -0.716567f, 0.681718f),
NzVector3f(0.000000f, -0.525731f, 0.850651f),
NzVector3f(0.425325f, -0.688191f, 0.587785f),
NzVector3f(0.587785f, -0.425325f, 0.688191f),
NzVector3f(0.688191f, -0.587785f, 0.425325f),
NzVector3f(-0.955423f, 0.295242f, 0.000000f),
NzVector3f(-0.951056f, 0.162460f, 0.262866f),
NzVector3f(-1.000000f, 0.000000f, 0.000000f),
NzVector3f(-0.850651f, 0.000000f, 0.525731f),
NzVector3f(-0.955423f, -0.295242f, 0.000000f),
NzVector3f(-0.951056f, -0.162460f, 0.262866f),
NzVector3f(-0.864188f, 0.442863f, -0.238856f),
NzVector3f(-0.951056f, 0.162460f, -0.262866f),
NzVector3f(-0.809017f, 0.309017f, -0.500000f),
NzVector3f(-0.864188f, -0.442863f, -0.238856f),
NzVector3f(-0.951056f, -0.162460f, -0.262866f),
NzVector3f(-0.809017f, -0.309017f, -0.500000f),
NzVector3f(-0.681718f, 0.147621f, -0.716567f),
NzVector3f(-0.681718f, -0.147621f, -0.716567f),
NzVector3f(-0.850651f, 0.000000f, -0.525731f),
NzVector3f(-0.688191f, 0.587785f, -0.425325f),
NzVector3f(-0.587785f, 0.425325f, -0.688191f),
NzVector3f(-0.425325f, 0.688191f, -0.587785f),
NzVector3f(-0.425325f, -0.688191f, -0.587785f),
NzVector3f(-0.587785f, -0.425325f, -0.688191f),
NzVector3f(-0.688191f, -0.587785f, -0.425325f)
};
const UInt32 md2Ident = 'I' + ('D'<<8) + ('P'<<16) + ('2'<<24);
const Vector3f md2Normals[162] =
{
Vector3f(-0.525731f, 0.000000f, 0.850651f),
Vector3f(-0.442863f, 0.238856f, 0.864188f),
Vector3f(-0.295242f, 0.000000f, 0.955423f),
Vector3f(-0.309017f, 0.500000f, 0.809017f),
Vector3f(-0.162460f, 0.262866f, 0.951056f),
Vector3f(0.000000f, 0.000000f, 1.000000f),
Vector3f(0.000000f, 0.850651f, 0.525731f),
Vector3f(-0.147621f, 0.716567f, 0.681718f),
Vector3f(0.147621f, 0.716567f, 0.681718f),
Vector3f(0.000000f, 0.525731f, 0.850651f),
Vector3f(0.309017f, 0.500000f, 0.809017f),
Vector3f(0.525731f, 0.000000f, 0.850651f),
Vector3f(0.295242f, 0.000000f, 0.955423f),
Vector3f(0.442863f, 0.238856f, 0.864188f),
Vector3f(0.162460f, 0.262866f, 0.951056f),
Vector3f(-0.681718f, 0.147621f, 0.716567f),
Vector3f(-0.809017f, 0.309017f, 0.500000f),
Vector3f(-0.587785f, 0.425325f, 0.688191f),
Vector3f(-0.850651f, 0.525731f, 0.000000f),
Vector3f(-0.864188f, 0.442863f, 0.238856f),
Vector3f(-0.716567f, 0.681718f, 0.147621f),
Vector3f(-0.688191f, 0.587785f, 0.425325f),
Vector3f(-0.500000f, 0.809017f, 0.309017f),
Vector3f(-0.238856f, 0.864188f, 0.442863f),
Vector3f(-0.425325f, 0.688191f, 0.587785f),
Vector3f(-0.716567f, 0.681718f, -0.147621f),
Vector3f(-0.500000f, 0.809017f, -0.309017f),
Vector3f(-0.525731f, 0.850651f, 0.000000f),
Vector3f(0.000000f, 0.850651f, -0.525731f),
Vector3f(-0.238856f, 0.864188f, -0.442863f),
Vector3f(0.000000f, 0.955423f, -0.295242f),
Vector3f(-0.262866f, 0.951056f, -0.162460f),
Vector3f(0.000000f, 1.000000f, 0.000000f),
Vector3f(0.000000f, 0.955423f, 0.295242f),
Vector3f(-0.262866f, 0.951056f, 0.162460f),
Vector3f(0.238856f, 0.864188f, 0.442863f),
Vector3f(0.262866f, 0.951056f, 0.162460f),
Vector3f(0.500000f, 0.809017f, 0.309017f),
Vector3f(0.238856f, 0.864188f, -0.442863f),
Vector3f(0.262866f, 0.951056f, -0.162460f),
Vector3f(0.500000f, 0.809017f, -0.309017f),
Vector3f(0.850651f, 0.525731f, 0.000000f),
Vector3f(0.716567f, 0.681718f, 0.147621f),
Vector3f(0.716567f, 0.681718f, -0.147621f),
Vector3f(0.525731f, 0.850651f, 0.000000f),
Vector3f(0.425325f, 0.688191f, 0.587785f),
Vector3f(0.864188f, 0.442863f, 0.238856f),
Vector3f(0.688191f, 0.587785f, 0.425325f),
Vector3f(0.809017f, 0.309017f, 0.500000f),
Vector3f(0.681718f, 0.147621f, 0.716567f),
Vector3f(0.587785f, 0.425325f, 0.688191f),
Vector3f(0.955423f, 0.295242f, 0.000000f),
Vector3f(1.000000f, 0.000000f, 0.000000f),
Vector3f(0.951056f, 0.162460f, 0.262866f),
Vector3f(0.850651f, -0.525731f, 0.000000f),
Vector3f(0.955423f, -0.295242f, 0.000000f),
Vector3f(0.864188f, -0.442863f, 0.238856f),
Vector3f(0.951056f, -0.162460f, 0.262866f),
Vector3f(0.809017f, -0.309017f, 0.500000f),
Vector3f(0.681718f, -0.147621f, 0.716567f),
Vector3f(0.850651f, 0.000000f, 0.525731f),
Vector3f(0.864188f, 0.442863f, -0.238856f),
Vector3f(0.809017f, 0.309017f, -0.500000f),
Vector3f(0.951056f, 0.162460f, -0.262866f),
Vector3f(0.525731f, 0.000000f, -0.850651f),
Vector3f(0.681718f, 0.147621f, -0.716567f),
Vector3f(0.681718f, -0.147621f, -0.716567f),
Vector3f(0.850651f, 0.000000f, -0.525731f),
Vector3f(0.809017f, -0.309017f, -0.500000f),
Vector3f(0.864188f, -0.442863f, -0.238856f),
Vector3f(0.951056f, -0.162460f, -0.262866f),
Vector3f(0.147621f, 0.716567f, -0.681718f),
Vector3f(0.309017f, 0.500000f, -0.809017f),
Vector3f(0.425325f, 0.688191f, -0.587785f),
Vector3f(0.442863f, 0.238856f, -0.864188f),
Vector3f(0.587785f, 0.425325f, -0.688191f),
Vector3f(0.688191f, 0.587785f, -0.425325f),
Vector3f(-0.147621f, 0.716567f, -0.681718f),
Vector3f(-0.309017f, 0.500000f, -0.809017f),
Vector3f(0.000000f, 0.525731f, -0.850651f),
Vector3f(-0.525731f, 0.000000f, -0.850651f),
Vector3f(-0.442863f, 0.238856f, -0.864188f),
Vector3f(-0.295242f, 0.000000f, -0.955423f),
Vector3f(-0.162460f, 0.262866f, -0.951056f),
Vector3f(0.000000f, 0.000000f, -1.000000f),
Vector3f(0.295242f, 0.000000f, -0.955423f),
Vector3f(0.162460f, 0.262866f, -0.951056f),
Vector3f(-0.442863f, -0.238856f, -0.864188f),
Vector3f(-0.309017f, -0.500000f, -0.809017f),
Vector3f(-0.162460f, -0.262866f, -0.951056f),
Vector3f(0.000000f, -0.850651f, -0.525731f),
Vector3f(-0.147621f, -0.716567f, -0.681718f),
Vector3f(0.147621f, -0.716567f, -0.681718f),
Vector3f(0.000000f, -0.525731f, -0.850651f),
Vector3f(0.309017f, -0.500000f, -0.809017f),
Vector3f(0.442863f, -0.238856f, -0.864188f),
Vector3f(0.162460f, -0.262866f, -0.951056f),
Vector3f(0.238856f, -0.864188f, -0.442863f),
Vector3f(0.500000f, -0.809017f, -0.309017f),
Vector3f(0.425325f, -0.688191f, -0.587785f),
Vector3f(0.716567f, -0.681718f, -0.147621f),
Vector3f(0.688191f, -0.587785f, -0.425325f),
Vector3f(0.587785f, -0.425325f, -0.688191f),
Vector3f(0.000000f, -0.955423f, -0.295242f),
Vector3f(0.000000f, -1.000000f, 0.000000f),
Vector3f(0.262866f, -0.951056f, -0.162460f),
Vector3f(0.000000f, -0.850651f, 0.525731f),
Vector3f(0.000000f, -0.955423f, 0.295242f),
Vector3f(0.238856f, -0.864188f, 0.442863f),
Vector3f(0.262866f, -0.951056f, 0.162460f),
Vector3f(0.500000f, -0.809017f, 0.309017f),
Vector3f(0.716567f, -0.681718f, 0.147621f),
Vector3f(0.525731f, -0.850651f, 0.000000f),
Vector3f(-0.238856f, -0.864188f, -0.442863f),
Vector3f(-0.500000f, -0.809017f, -0.309017f),
Vector3f(-0.262866f, -0.951056f, -0.162460f),
Vector3f(-0.850651f, -0.525731f, 0.000000f),
Vector3f(-0.716567f, -0.681718f, -0.147621f),
Vector3f(-0.716567f, -0.681718f, 0.147621f),
Vector3f(-0.525731f, -0.850651f, 0.000000f),
Vector3f(-0.500000f, -0.809017f, 0.309017f),
Vector3f(-0.238856f, -0.864188f, 0.442863f),
Vector3f(-0.262866f, -0.951056f, 0.162460f),
Vector3f(-0.864188f, -0.442863f, 0.238856f),
Vector3f(-0.809017f, -0.309017f, 0.500000f),
Vector3f(-0.688191f, -0.587785f, 0.425325f),
Vector3f(-0.681718f, -0.147621f, 0.716567f),
Vector3f(-0.442863f, -0.238856f, 0.864188f),
Vector3f(-0.587785f, -0.425325f, 0.688191f),
Vector3f(-0.309017f, -0.500000f, 0.809017f),
Vector3f(-0.147621f, -0.716567f, 0.681718f),
Vector3f(-0.425325f, -0.688191f, 0.587785f),
Vector3f(-0.162460f, -0.262866f, 0.951056f),
Vector3f(0.442863f, -0.238856f, 0.864188f),
Vector3f(0.162460f, -0.262866f, 0.951056f),
Vector3f(0.309017f, -0.500000f, 0.809017f),
Vector3f(0.147621f, -0.716567f, 0.681718f),
Vector3f(0.000000f, -0.525731f, 0.850651f),
Vector3f(0.425325f, -0.688191f, 0.587785f),
Vector3f(0.587785f, -0.425325f, 0.688191f),
Vector3f(0.688191f, -0.587785f, 0.425325f),
Vector3f(-0.955423f, 0.295242f, 0.000000f),
Vector3f(-0.951056f, 0.162460f, 0.262866f),
Vector3f(-1.000000f, 0.000000f, 0.000000f),
Vector3f(-0.850651f, 0.000000f, 0.525731f),
Vector3f(-0.955423f, -0.295242f, 0.000000f),
Vector3f(-0.951056f, -0.162460f, 0.262866f),
Vector3f(-0.864188f, 0.442863f, -0.238856f),
Vector3f(-0.951056f, 0.162460f, -0.262866f),
Vector3f(-0.809017f, 0.309017f, -0.500000f),
Vector3f(-0.864188f, -0.442863f, -0.238856f),
Vector3f(-0.951056f, -0.162460f, -0.262866f),
Vector3f(-0.809017f, -0.309017f, -0.500000f),
Vector3f(-0.681718f, 0.147621f, -0.716567f),
Vector3f(-0.681718f, -0.147621f, -0.716567f),
Vector3f(-0.850651f, 0.000000f, -0.525731f),
Vector3f(-0.688191f, 0.587785f, -0.425325f),
Vector3f(-0.587785f, 0.425325f, -0.688191f),
Vector3f(-0.425325f, 0.688191f, -0.587785f),
Vector3f(-0.425325f, -0.688191f, -0.587785f),
Vector3f(-0.587785f, -0.425325f, -0.688191f),
Vector3f(-0.688191f, -0.587785f, -0.425325f)
};
}

View File

@@ -8,57 +8,60 @@
#include <Nazara/Prerequesites.hpp>
#include <Nazara/Math/Vector3.hpp>
struct MD2_Header
namespace Nz
{
nzUInt32 ident; // nombre magique : "IDP2"
nzUInt32 version; // version du format : 8
struct MD2_Header
{
UInt32 ident; // nombre magique : "IDP2"
UInt32 version; // version du format : 8
nzUInt32 skinwidth; // largeur texture
nzUInt32 skinheight; // hauteur texture
UInt32 skinwidth; // largeur texture
UInt32 skinheight; // hauteur texture
nzUInt32 framesize; // taille d'une frame en octets
UInt32 framesize; // taille d'une frame en octets
nzUInt32 num_skins; // nombre de skins
nzUInt32 num_vertices; // nombre de vertices par frame
nzUInt32 num_st; // nombre de coordonnées de texture
nzUInt32 num_tris; // nombre de triangles
nzUInt32 num_glcmds; // nombre de commandes opengl
nzUInt32 num_frames; // nombre de frames
UInt32 num_skins; // nombre de skins
UInt32 num_vertices; // nombre de vertices par frame
UInt32 num_st; // nombre de coordonnées de texture
UInt32 num_tris; // nombre de triangles
UInt32 num_glcmds; // nombre de commandes opengl
UInt32 num_frames; // nombre de frames
nzUInt32 offset_skins; // offset données skins
nzUInt32 offset_st; // offset données coordonnées de texture
nzUInt32 offset_tris; // offset données triangles
nzUInt32 offset_frames; // offset données frames
nzUInt32 offset_glcmds; // offset données commandes OpenGL
nzUInt32 offset_end; // offset fin de fichier
};
UInt32 offset_skins; // offset données skins
UInt32 offset_st; // offset données coordonnées de texture
UInt32 offset_tris; // offset données triangles
UInt32 offset_frames; // offset données frames
UInt32 offset_glcmds; // offset données commandes OpenGL
UInt32 offset_end; // offset fin de fichier
};
static_assert(sizeof(MD2_Header) == 17*sizeof(nzUInt32), "MD2_Header must be packed");
static_assert(sizeof(MD2_Header) == 17*sizeof(UInt32), "MD2_Header must be packed");
struct MD2_Vertex
{
nzUInt8 x, y, z;
nzUInt8 n;
};
struct MD2_Vertex
{
UInt8 x, y, z;
UInt8 n;
};
static_assert(sizeof(MD2_Vertex) == 4*sizeof(nzUInt8), "MD2_Vertex must be packed");
static_assert(sizeof(MD2_Vertex) == 4*sizeof(UInt8), "MD2_Vertex must be packed");
struct MD2_TexCoord
{
nzInt16 u, v;
};
struct MD2_TexCoord
{
Int16 u, v;
};
static_assert(sizeof(MD2_TexCoord) == 2*sizeof(nzInt16), "MD2_TexCoord must be packed");
static_assert(sizeof(MD2_TexCoord) == 2*sizeof(Int16), "MD2_TexCoord must be packed");
struct MD2_Triangle
{
nzUInt16 vertices[3];
nzUInt16 texCoords[3];
};
struct MD2_Triangle
{
UInt16 vertices[3];
UInt16 texCoords[3];
};
static_assert(sizeof(MD2_Triangle) == 2*3*sizeof(nzUInt16), "MD2_Triangle must be packed");
static_assert(sizeof(MD2_Triangle) == 2*3*sizeof(UInt16), "MD2_Triangle must be packed");
extern const nzUInt32 md2Ident;
extern const NzVector3f md2Normals[162];
extern const UInt32 md2Ident;
extern const Vector3f md2Normals[162];
}
#endif // NAZARA_LOADERS_MD2_CONSTANTS_HPP

View File

@@ -17,230 +17,236 @@
#include <memory>
#include <Nazara/Utility/Debug.hpp>
namespace
namespace Nz
{
bool IsSupported(const NzString& extension)
namespace
{
return (extension == "md2");
}
nzTernary Check(NzInputStream& stream, const NzMeshParams& parameters)
{
NazaraUnused(parameters);
nzUInt32 magic[2];
if (stream.Read(&magic[0], 2*sizeof(nzUInt32)) == 2*sizeof(nzUInt32))
bool IsSupported(const String& extension)
{
return (extension == "md2");
}
Ternary Check(InputStream& stream, const MeshParams& parameters)
{
NazaraUnused(parameters);
UInt32 magic[2];
if (stream.Read(&magic[0], 2*sizeof(UInt32)) == 2*sizeof(UInt32))
{
#ifdef NAZARA_BIG_ENDIAN
SwapBytes(&magic[0], sizeof(UInt32));
SwapBytes(&magic[1], sizeof(UInt32));
#endif
if (magic[0] == md2Ident && magic[1] == 8)
return Ternary_True;
}
return Ternary_False;
}
bool Load(Mesh* mesh, InputStream& stream, const MeshParams& parameters)
{
MD2_Header header;
if (stream.Read(&header, sizeof(MD2_Header)) != sizeof(MD2_Header))
{
NazaraError("Failed to read header");
return false;
}
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(&magic[0], sizeof(nzUInt32));
NzByteSwap(&magic[1], sizeof(nzUInt32));
SwapBytes(&header.skinwidth, sizeof(UInt32));
SwapBytes(&header.skinheight, sizeof(UInt32));
SwapBytes(&header.framesize, sizeof(UInt32));
SwapBytes(&header.num_skins, sizeof(UInt32));
SwapBytes(&header.num_vertices, sizeof(UInt32));
SwapBytes(&header.num_st, sizeof(UInt32));
SwapBytes(&header.num_tris, sizeof(UInt32));
SwapBytes(&header.num_glcmds, sizeof(UInt32));
SwapBytes(&header.num_frames, sizeof(UInt32));
SwapBytes(&header.offset_skins, sizeof(UInt32));
SwapBytes(&header.offset_st, sizeof(UInt32));
SwapBytes(&header.offset_tris, sizeof(UInt32));
SwapBytes(&header.offset_frames, sizeof(UInt32));
SwapBytes(&header.offset_glcmds, sizeof(UInt32));
SwapBytes(&header.offset_end, sizeof(UInt32));
#endif
if (magic[0] == md2Ident && magic[1] == 8)
return nzTernary_True;
}
return nzTernary_False;
}
bool Load(NzMesh* mesh, NzInputStream& stream, const NzMeshParams& parameters)
{
MD2_Header header;
if (stream.Read(&header, sizeof(MD2_Header)) != sizeof(MD2_Header))
{
NazaraError("Failed to read header");
return false;
}
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(&header.skinwidth, sizeof(nzUInt32));
NzByteSwap(&header.skinheight, sizeof(nzUInt32));
NzByteSwap(&header.framesize, sizeof(nzUInt32));
NzByteSwap(&header.num_skins, sizeof(nzUInt32));
NzByteSwap(&header.num_vertices, sizeof(nzUInt32));
NzByteSwap(&header.num_st, sizeof(nzUInt32));
NzByteSwap(&header.num_tris, sizeof(nzUInt32));
NzByteSwap(&header.num_glcmds, sizeof(nzUInt32));
NzByteSwap(&header.num_frames, sizeof(nzUInt32));
NzByteSwap(&header.offset_skins, sizeof(nzUInt32));
NzByteSwap(&header.offset_st, sizeof(nzUInt32));
NzByteSwap(&header.offset_tris, sizeof(nzUInt32));
NzByteSwap(&header.offset_frames, sizeof(nzUInt32));
NzByteSwap(&header.offset_glcmds, sizeof(nzUInt32));
NzByteSwap(&header.offset_end, sizeof(nzUInt32));
#endif
if (stream.GetSize() < header.offset_end)
{
NazaraError("Incomplete MD2 file");
return false;
}
/// Création du mesh
// Le moteur ne supporte plus les animations image-clé, nous ne pouvons charger qu'en statique
if (!mesh->CreateStatic()) // Ne devrait jamais échouer
{
NazaraInternalError("Failed to create mesh");
return false;
}
/// Chargement des skins
if (header.num_skins > 0)
{
mesh->SetMaterialCount(header.num_skins);
stream.SetCursorPos(header.offset_skins);
if (stream.GetSize() < header.offset_end)
{
NzString baseDir = stream.GetDirectory();
char skin[68];
for (unsigned int i = 0; i < header.num_skins; ++i)
NazaraError("Incomplete MD2 file");
return false;
}
/// Création du mesh
// Le moteur ne supporte plus les animations image-clé, nous ne pouvons charger qu'en statique
if (!mesh->CreateStatic()) // Ne devrait jamais échouer
{
NazaraInternalError("Failed to create mesh");
return false;
}
/// Chargement des skins
if (header.num_skins > 0)
{
mesh->SetMaterialCount(header.num_skins);
stream.SetCursorPos(header.offset_skins);
{
stream.Read(skin, 68*sizeof(char));
mesh->SetMaterial(i, baseDir + skin);
String baseDir = stream.GetDirectory();
char skin[68];
for (unsigned int i = 0; i < header.num_skins; ++i)
{
stream.Read(skin, 68*sizeof(char));
mesh->SetMaterial(i, baseDir + skin);
}
}
}
}
/// Chargement des submesh
// Actuellement le loader ne charge qu'un submesh
NzIndexBufferRef indexBuffer = NzIndexBuffer::New(false, header.num_tris*3, parameters.storage, nzBufferUsage_Static);
/// Chargement des submesh
// Actuellement le loader ne charge qu'un submesh
IndexBufferRef indexBuffer = IndexBuffer::New(false, header.num_tris*3, parameters.storage, BufferUsage_Static);
/// Lecture des triangles
std::vector<MD2_Triangle> triangles(header.num_tris);
/// Lecture des triangles
std::vector<MD2_Triangle> triangles(header.num_tris);
stream.SetCursorPos(header.offset_tris);
stream.Read(&triangles[0], header.num_tris*sizeof(MD2_Triangle));
stream.SetCursorPos(header.offset_tris);
stream.Read(&triangles[0], header.num_tris*sizeof(MD2_Triangle));
NzBufferMapper<NzIndexBuffer> indexMapper(indexBuffer, nzBufferAccess_DiscardAndWrite);
nzUInt16* index = reinterpret_cast<nzUInt16*>(indexMapper.GetPointer());
BufferMapper<IndexBuffer> indexMapper(indexBuffer, BufferAccess_DiscardAndWrite);
UInt16* index = reinterpret_cast<UInt16*>(indexMapper.GetPointer());
for (unsigned int i = 0; i < header.num_tris; ++i)
{
#ifdef NAZARA_BIG_ENDIAN
SwapBytes(&triangles[i].vertices[0], sizeof(UInt16));
SwapBytes(&triangles[i].texCoords[0], sizeof(UInt16));
SwapBytes(&triangles[i].vertices[1], sizeof(UInt16));
SwapBytes(&triangles[i].texCoords[1], sizeof(UInt16));
SwapBytes(&triangles[i].vertices[2], sizeof(UInt16));
SwapBytes(&triangles[i].texCoords[2], sizeof(UInt16));
#endif
// On respécifie le triangle dans l'ordre attendu
*index++ = triangles[i].vertices[0];
*index++ = triangles[i].vertices[2];
*index++ = triangles[i].vertices[1];
}
indexMapper.Unmap();
if (parameters.optimizeIndexBuffers)
indexBuffer->Optimize();
/// Lecture des coordonnées de texture
std::vector<MD2_TexCoord> texCoords(header.num_st);
stream.SetCursorPos(header.offset_st);
stream.Read(&texCoords[0], header.num_st*sizeof(MD2_TexCoord));
for (unsigned int i = 0; i < header.num_tris; ++i)
{
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(&triangles[i].vertices[0], sizeof(nzUInt16));
NzByteSwap(&triangles[i].texCoords[0], sizeof(nzUInt16));
NzByteSwap(&triangles[i].vertices[1], sizeof(nzUInt16));
NzByteSwap(&triangles[i].texCoords[1], sizeof(nzUInt16));
NzByteSwap(&triangles[i].vertices[2], sizeof(nzUInt16));
NzByteSwap(&triangles[i].texCoords[2], sizeof(nzUInt16));
for (unsigned int i = 0; i < header.num_st; ++i)
{
SwapBytes(&texCoords[i].u, sizeof(Int16));
SwapBytes(&texCoords[i].v, sizeof(Int16));
}
#endif
// On respécifie le triangle dans l'ordre attendu
*index++ = triangles[i].vertices[0];
*index++ = triangles[i].vertices[2];
*index++ = triangles[i].vertices[1];
}
indexMapper.Unmap();
if (parameters.optimizeIndexBuffers)
indexBuffer->Optimize();
/// Lecture des coordonnées de texture
std::vector<MD2_TexCoord> texCoords(header.num_st);
stream.SetCursorPos(header.offset_st);
stream.Read(&texCoords[0], header.num_st*sizeof(MD2_TexCoord));
#ifdef NAZARA_BIG_ENDIAN
for (unsigned int i = 0; i < header.num_st; ++i)
{
NzByteSwap(&texCoords[i].u, sizeof(nzInt16));
NzByteSwap(&texCoords[i].v, sizeof(nzInt16));
}
#endif
NzVertexBufferRef vertexBuffer = NzVertexBuffer::New(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent), header.num_vertices, parameters.storage, nzBufferUsage_Static);
NzStaticMeshRef subMesh = NzStaticMesh::New(mesh);
if (!subMesh->Create(vertexBuffer))
{
NazaraError("Failed to create SubMesh");
return false;
}
/// Chargement des vertices
stream.SetCursorPos(header.offset_frames);
std::unique_ptr<MD2_Vertex[]> vertices(new MD2_Vertex[header.num_vertices]);
NzVector3f scale, translate;
stream.Read(scale, sizeof(NzVector3f));
stream.Read(translate, sizeof(NzVector3f));
stream.Read(nullptr, 16*sizeof(char)); // Nom de la frame, inutile ici
stream.Read(vertices.get(), header.num_vertices*sizeof(MD2_Vertex));
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(&scale.x, sizeof(float));
NzByteSwap(&scale.y, sizeof(float));
NzByteSwap(&scale.z, sizeof(float));
NzByteSwap(&translate.x, sizeof(float));
NzByteSwap(&translate.y, sizeof(float));
NzByteSwap(&translate.z, sizeof(float));
#endif
// Un personnage de taille moyenne fait ~50 unités de haut dans Quake 2
// Avec Nazara, 1 unité = 1 mètre, nous devons donc adapter l'échelle
NzVector3f s(parameters.scale/29.f); // 50/29 = 1.72 (Soit 1.72 mètre, proche de la taille moyenne d'un individu)
scale *= s;
translate *= s;
NzBufferMapper<NzVertexBuffer> vertexMapper(vertexBuffer, nzBufferAccess_DiscardAndWrite);
NzMeshVertex* vertex = reinterpret_cast<NzMeshVertex*>(vertexMapper.GetPointer());
/// Chargement des coordonnées de texture
const unsigned int indexFix[3] = {0, 2, 1}; // Pour respécifier les indices dans le bon ordre
for (unsigned int i = 0; i < header.num_tris; ++i)
{
for (unsigned int j = 0; j < 3; ++j)
VertexBufferRef vertexBuffer = VertexBuffer::New(VertexDeclaration::Get(VertexLayout_XYZ_Normal_UV_Tangent), header.num_vertices, parameters.storage, BufferUsage_Static);
StaticMeshRef subMesh = StaticMesh::New(mesh);
if (!subMesh->Create(vertexBuffer))
{
const unsigned int fixedIndex = indexFix[j];
const MD2_TexCoord& texC = texCoords[triangles[i].texCoords[fixedIndex]];
float u = static_cast<float>(texC.u) / header.skinwidth;
float v = static_cast<float>(texC.v) / header.skinheight;
vertex[triangles[i].vertices[fixedIndex]].uv.Set(u, (parameters.flipUVs) ? 1.f - v : v);
NazaraError("Failed to create SubMesh");
return false;
}
/// Chargement des vertices
stream.SetCursorPos(header.offset_frames);
std::unique_ptr<MD2_Vertex[]> vertices(new MD2_Vertex[header.num_vertices]);
Vector3f scale, translate;
stream.Read(scale, sizeof(Vector3f));
stream.Read(translate, sizeof(Vector3f));
stream.Read(nullptr, 16*sizeof(char)); // Nom de la frame, inutile ici
stream.Read(vertices.get(), header.num_vertices*sizeof(MD2_Vertex));
#ifdef NAZARA_BIG_ENDIAN
SwapBytes(&scale.x, sizeof(float));
SwapBytes(&scale.y, sizeof(float));
SwapBytes(&scale.z, sizeof(float));
SwapBytes(&translate.x, sizeof(float));
SwapBytes(&translate.y, sizeof(float));
SwapBytes(&translate.z, sizeof(float));
#endif
// Un personnage de taille moyenne fait ~50 unités de haut dans Quake 2
// Avec Nazara, 1 unité = 1 mètre, nous devons donc adapter l'échelle
Vector3f s(parameters.scale/29.f); // 50/29 = 1.72 (Soit 1.72 mètre, proche de la taille moyenne d'un individu)
scale *= s;
translate *= s;
BufferMapper<VertexBuffer> vertexMapper(vertexBuffer, BufferAccess_DiscardAndWrite);
MeshVertex* vertex = reinterpret_cast<MeshVertex*>(vertexMapper.GetPointer());
/// Chargement des coordonnées de texture
const unsigned int indexFix[3] = {0, 2, 1}; // Pour respécifier les indices dans le bon ordre
for (unsigned int i = 0; i < header.num_tris; ++i)
{
for (unsigned int j = 0; j < 3; ++j)
{
const unsigned int fixedIndex = indexFix[j];
const MD2_TexCoord& texC = texCoords[triangles[i].texCoords[fixedIndex]];
float u = static_cast<float>(texC.u) / header.skinwidth;
float v = static_cast<float>(texC.v) / header.skinheight;
vertex[triangles[i].vertices[fixedIndex]].uv.Set(u, (parameters.flipUVs) ? 1.f - v : v);
}
}
/// Chargement des positions
// Pour que le modèle soit correctement aligné, on génère un quaternion que nous appliquerons à chacune des vertices
Quaternionf rotationQuat = EulerAnglesf(-90.f, 90.f, 0.f);
for (unsigned int v = 0; v < header.num_vertices; ++v)
{
const MD2_Vertex& vert = vertices[v];
Vector3f position = rotationQuat * Vector3f(vert.x*scale.x + translate.x, vert.y*scale.y + translate.y, vert.z*scale.z + translate.z);
vertex->position = position;
vertex->normal = rotationQuat * md2Normals[vert.n];
vertex++;
}
vertexMapper.Unmap();
subMesh->SetIndexBuffer(indexBuffer);
subMesh->SetMaterialIndex(0);
subMesh->GenerateAABB();
subMesh->GenerateTangents();
mesh->AddSubMesh(subMesh);
if (parameters.center)
mesh->Recenter();
return true;
}
}
/// Chargement des positions
// Pour que le modèle soit correctement aligné, on génère un quaternion que nous appliquerons à chacune des vertices
NzQuaternionf rotationQuat = NzEulerAnglesf(-90.f, 90.f, 0.f);
for (unsigned int v = 0; v < header.num_vertices; ++v)
namespace Loaders
{
void RegisterMD2()
{
const MD2_Vertex& vert = vertices[v];
NzVector3f position = rotationQuat * NzVector3f(vert.x*scale.x + translate.x, vert.y*scale.y + translate.y, vert.z*scale.z + translate.z);
vertex->position = position;
vertex->normal = rotationQuat * md2Normals[vert.n];
vertex++;
MeshLoader::RegisterLoader(IsSupported, Check, Load);
}
vertexMapper.Unmap();
subMesh->SetIndexBuffer(indexBuffer);
subMesh->SetMaterialIndex(0);
subMesh->GenerateAABB();
subMesh->GenerateTangents();
mesh->AddSubMesh(subMesh);
if (parameters.center)
mesh->Recenter();
return true;
void UnregisterMD2()
{
MeshLoader::UnregisterLoader(IsSupported, Check, Load);
}
}
}
void NzLoaders_MD2_Register()
{
NzMeshLoader::RegisterLoader(IsSupported, Check, Load);
}
void NzLoaders_MD2_Unregister()
{
NzMeshLoader::UnregisterLoader(IsSupported, Check, Load);
}

View File

@@ -9,7 +9,13 @@
#include <Nazara/Prerequesites.hpp>
void NzLoaders_MD2_Register();
void NzLoaders_MD2_Unregister();
namespace Nz
{
namespace Loaders
{
void RegisterMD2();
void UnregisterMD2();
}
}
#endif // NAZARA_LOADERS_MD2_HPP

View File

@@ -6,87 +6,93 @@
#include <Nazara/Utility/Formats/MD5AnimParser.hpp>
#include <Nazara/Utility/Debug.hpp>
namespace
namespace Nz
{
bool IsSupported(const NzString& extension)
namespace
{
return (extension == "md5anim");
}
nzTernary Check(NzInputStream& stream, const NzAnimationParams& parameters)
{
NazaraUnused(parameters);
NzMD5AnimParser parser(stream);
return parser.Check();
}
bool Load(NzAnimation* animation, NzInputStream& stream, const NzAnimationParams& parameters)
{
///TODO: Utiliser les paramètres
NzMD5AnimParser parser(stream);
if (!parser.Parse())
bool IsSupported(const String& extension)
{
NazaraError("MD5Anim parser failed");
return false;
return (extension == "md5anim");
}
const NzMD5AnimParser::Frame* frames = parser.GetFrames();
unsigned int frameCount = parser.GetFrameCount();
unsigned int frameRate = parser.GetFrameRate();
const NzMD5AnimParser::Joint* joints = parser.GetJoints();
unsigned int jointCount = parser.GetJointCount();
// À ce stade, nous sommes censés avoir assez d'informations pour créer l'animation
animation->CreateSkeletal(frameCount, jointCount);
NzSequence sequence;
sequence.firstFrame = 0;
sequence.frameCount = frameCount;
sequence.frameRate = frameRate;
sequence.name = stream.GetPath().SubStringFrom(NAZARA_DIRECTORY_SEPARATOR, -1, true);
animation->AddSequence(sequence);
NzSequenceJoint* sequenceJoints = animation->GetSequenceJoints();
// Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette
NzQuaternionf rotationQuat = NzQuaternionf::RotationBetween(NzVector3f::UnitX(), NzVector3f::Forward()) *
NzQuaternionf::RotationBetween(NzVector3f::UnitZ(), NzVector3f::Up());
for (unsigned int i = 0; i < jointCount; ++i)
Ternary Check(InputStream& stream, const AnimationParams& parameters)
{
int parent = joints[i].parent;
for (unsigned int j = 0; j < frameCount; ++j)
NazaraUnused(parameters);
MD5AnimParser parser(stream);
return parser.Check();
}
bool Load(Animation* animation, InputStream& stream, const AnimationParams& parameters)
{
///TODO: Utiliser les paramètres
MD5AnimParser parser(stream);
if (!parser.Parse())
{
NzSequenceJoint& sequenceJoint = sequenceJoints[j*jointCount + i];
if (parent >= 0)
{
sequenceJoint.position = frames[j].joints[i].pos;
sequenceJoint.rotation = frames[j].joints[i].orient;
}
else
{
sequenceJoint.position = rotationQuat * frames[j].joints[i].pos;
sequenceJoint.rotation = rotationQuat * frames[j].joints[i].orient;
}
sequenceJoint.scale.Set(1.f);
NazaraError("MD5Anim parser failed");
return false;
}
const MD5AnimParser::Frame* frames = parser.GetFrames();
unsigned int frameCount = parser.GetFrameCount();
unsigned int frameRate = parser.GetFrameRate();
const MD5AnimParser::Joint* joints = parser.GetJoints();
unsigned int jointCount = parser.GetJointCount();
// À ce stade, nous sommes censés avoir assez d'informations pour créer l'animation
animation->CreateSkeletal(frameCount, jointCount);
Sequence sequence;
sequence.firstFrame = 0;
sequence.frameCount = frameCount;
sequence.frameRate = frameRate;
sequence.name = stream.GetPath().SubStringFrom(NAZARA_DIRECTORY_SEPARATOR, -1, true);
animation->AddSequence(sequence);
SequenceJoint* sequenceJoints = animation->GetSequenceJoints();
// Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette
Quaternionf rotationQuat = Quaternionf::RotationBetween(Vector3f::UnitX(), Vector3f::Forward()) *
Quaternionf::RotationBetween(Vector3f::UnitZ(), Vector3f::Up());
for (unsigned int i = 0; i < jointCount; ++i)
{
int parent = joints[i].parent;
for (unsigned int j = 0; j < frameCount; ++j)
{
SequenceJoint& sequenceJoint = sequenceJoints[j*jointCount + i];
if (parent >= 0)
{
sequenceJoint.position = frames[j].joints[i].pos;
sequenceJoint.rotation = frames[j].joints[i].orient;
}
else
{
sequenceJoint.position = rotationQuat * frames[j].joints[i].pos;
sequenceJoint.rotation = rotationQuat * frames[j].joints[i].orient;
}
sequenceJoint.scale.Set(1.f);
}
}
return true;
}
}
namespace Loaders
{
void RegisterMD5Anim()
{
AnimationLoader::RegisterLoader(IsSupported, Check, Load);
}
return true;
void UnregisterMD5Anim()
{
AnimationLoader::UnregisterLoader(IsSupported, Check, Load);
}
}
}
void NzLoaders_MD5Anim_Register()
{
NzAnimationLoader::RegisterLoader(IsSupported, Check, Load);
}
void NzLoaders_MD5Anim_Unregister()
{
NzAnimationLoader::UnregisterLoader(IsSupported, Check, Load);
}

View File

@@ -9,7 +9,13 @@
#include <Nazara/Prerequesites.hpp>
void NzLoaders_MD5Anim_Register();
void NzLoaders_MD5Anim_Unregister();
namespace Nz
{
namespace Loaders
{
void RegisterMD5Anim();
void UnregisterMD5Anim();
}
}
#endif // NAZARA_LOADERS_MD5ANIM_HPP

View File

@@ -13,509 +13,512 @@
#include <limits>
#include <Nazara/Utility/Debug.hpp>
NzMD5AnimParser::NzMD5AnimParser(NzInputStream& stream) :
m_stream(stream),
m_keepLastLine(false),
m_frameIndex(0),
m_frameRate(0),
m_lineCount(0),
m_streamFlags(stream.GetStreamOptions())
namespace Nz
{
if ((m_streamFlags & nzStreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags | nzStreamOption_Text);
}
NzMD5AnimParser::~NzMD5AnimParser()
{
if ((m_streamFlags & nzStreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags);
}
nzTernary NzMD5AnimParser::Check()
{
if (Advance(false))
MD5AnimParser::MD5AnimParser(InputStream& stream) :
m_stream(stream),
m_keepLastLine(false),
m_frameIndex(0),
m_frameRate(0),
m_lineCount(0),
m_streamFlags(stream.GetStreamOptions())
{
unsigned int version;
if (std::sscanf(&m_currentLine[0], " MD5Version %u", &version) == 1)
{
if (version == 10)
return nzTernary_True;
}
if ((m_streamFlags & StreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags | StreamOption_Text);
}
return nzTernary_False;
}
unsigned int NzMD5AnimParser::GetAnimatedComponentCount() const
{
return m_animatedComponents.size();
}
const NzMD5AnimParser::Frame* NzMD5AnimParser::GetFrames() const
{
return m_frames.data();
}
unsigned int NzMD5AnimParser::GetFrameCount() const
{
return m_frames.size();
}
unsigned int NzMD5AnimParser::GetFrameRate() const
{
return m_frameRate;
}
const NzMD5AnimParser::Joint* NzMD5AnimParser::GetJoints() const
{
return m_joints.data();
}
unsigned int NzMD5AnimParser::GetJointCount() const
{
return m_joints.size();
}
bool NzMD5AnimParser::Parse()
{
while (Advance(false))
MD5AnimParser::~MD5AnimParser()
{
switch (m_currentLine[0])
if ((m_streamFlags & StreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags);
}
Ternary MD5AnimParser::Check()
{
if (Advance(false))
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
case 'M': // MD5Version
if (m_currentLine.GetWord(0) != "MD5Version")
UnrecognizedLine();
break;
#endif
case 'b': // baseframe/bounds
if (m_currentLine.StartsWith("baseframe {"))
{
if (!ParseBaseframe())
{
Error("Failed to parse baseframe");
return false;
}
}
else if (m_currentLine.StartsWith("bounds {"))
{
if (!ParseBounds())
{
Error("Failed to parse bounds");
return false;
}
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
case 'c': // commandline
if (m_currentLine.GetWord(0) != "commandline")
UnrecognizedLine();
break;
#endif
case 'f':
unsigned int version;
if (std::sscanf(&m_currentLine[0], " MD5Version %u", &version) == 1)
{
unsigned int index;
if (std::sscanf(&m_currentLine[0], "frame %u {", &index) == 1)
{
if (m_frameIndex != index)
{
Error("Unexpected frame index (expected " + NzString::Number(m_frameIndex) + ", got " + NzString::Number(index) + ')');
return false;
}
if (!ParseFrame())
{
Error("Failed to parse frame");
return false;
}
m_frameIndex++;
}
else if (std::sscanf(&m_currentLine[0], "frameRate %u", &m_frameRate) != 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
}
break;
if (version == 10)
return Ternary_True;
}
case 'h': // hierarchy
if (m_currentLine.StartsWith("hierarchy {"))
{
if (!ParseHierarchy())
{
Error("Failed to parse hierarchy");
return false;
}
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
case 'n': // num[Frames/Joints]
{
unsigned int count;
if (std::sscanf(&m_currentLine[0], "numAnimatedComponents %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_animatedComponents.empty())
Warning("Animated components count is already defined");
#endif
m_animatedComponents.resize(count);
}
else if (std::sscanf(&m_currentLine[0], "numFrames %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_frames.empty())
Warning("Frame count is already defined");
#endif
m_frames.resize(count);
}
else if (std::sscanf(&m_currentLine[0], "numJoints %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_joints.empty())
Warning("Joint count is already defined");
#endif
m_joints.resize(count);
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
}
default:
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
return Ternary_False;
}
unsigned int frameCount = m_frames.size();
if (frameCount == 0)
unsigned int MD5AnimParser::GetAnimatedComponentCount() const
{
NazaraError("Frame count is invalid or missing");
return false;
return m_animatedComponents.size();
}
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
const MD5AnimParser::Frame* MD5AnimParser::GetFrames() const
{
NazaraError("Joint count is invalid or missing");
return false;
return m_frames.data();
}
if (m_frameIndex != frameCount)
unsigned int MD5AnimParser::GetFrameCount() const
{
NazaraError("Missing frame infos: [" + NzString::Number(m_frameIndex) + ',' + NzString::Number(frameCount) + ']');
return false;
return m_frames.size();
}
if (m_frameRate == 0)
unsigned int MD5AnimParser::GetFrameRate() const
{
NazaraWarning("Framerate is either invalid or missing, assuming a default value of 24");
m_frameRate = 24;
return m_frameRate;
}
return true;
}
bool NzMD5AnimParser::Advance(bool required)
{
if (!m_keepLastLine)
const MD5AnimParser::Joint* MD5AnimParser::GetJoints() const
{
do
return m_joints.data();
}
unsigned int MD5AnimParser::GetJointCount() const
{
return m_joints.size();
}
bool MD5AnimParser::Parse()
{
while (Advance(false))
{
if (m_stream.EndOfStream())
switch (m_currentLine[0])
{
if (required)
Error("Incomplete MD5 file");
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
case 'M': // MD5Version
if (m_currentLine.GetWord(0) != "MD5Version")
UnrecognizedLine();
break;
#endif
case 'b': // baseframe/bounds
if (m_currentLine.StartsWith("baseframe {"))
{
if (!ParseBaseframe())
{
Error("Failed to parse baseframe");
return false;
}
}
else if (m_currentLine.StartsWith("bounds {"))
{
if (!ParseBounds())
{
Error("Failed to parse bounds");
return false;
}
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
case 'c': // commandline
if (m_currentLine.GetWord(0) != "commandline")
UnrecognizedLine();
break;
#endif
case 'f':
{
unsigned int index;
if (std::sscanf(&m_currentLine[0], "frame %u {", &index) == 1)
{
if (m_frameIndex != index)
{
Error("Unexpected frame index (expected " + String::Number(m_frameIndex) + ", got " + String::Number(index) + ')');
return false;
}
if (!ParseFrame())
{
Error("Failed to parse frame");
return false;
}
m_frameIndex++;
}
else if (std::sscanf(&m_currentLine[0], "frameRate %u", &m_frameRate) != 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
}
break;
}
case 'h': // hierarchy
if (m_currentLine.StartsWith("hierarchy {"))
{
if (!ParseHierarchy())
{
Error("Failed to parse hierarchy");
return false;
}
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
case 'n': // num[Frames/Joints]
{
unsigned int count;
if (std::sscanf(&m_currentLine[0], "numAnimatedComponents %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_animatedComponents.empty())
Warning("Animated components count is already defined");
#endif
m_animatedComponents.resize(count);
}
else if (std::sscanf(&m_currentLine[0], "numFrames %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_frames.empty())
Warning("Frame count is already defined");
#endif
m_frames.resize(count);
}
else if (std::sscanf(&m_currentLine[0], "numJoints %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_joints.empty())
Warning("Joint count is already defined");
#endif
m_joints.resize(count);
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
}
default:
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
}
unsigned int frameCount = m_frames.size();
if (frameCount == 0)
{
NazaraError("Frame count is invalid or missing");
return false;
}
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
NazaraError("Joint count is invalid or missing");
return false;
}
if (m_frameIndex != frameCount)
{
NazaraError("Missing frame infos: [" + String::Number(m_frameIndex) + ',' + String::Number(frameCount) + ']');
return false;
}
if (m_frameRate == 0)
{
NazaraWarning("Framerate is either invalid or missing, assuming a default value of 24");
m_frameRate = 24;
}
return true;
}
bool MD5AnimParser::Advance(bool required)
{
if (!m_keepLastLine)
{
do
{
if (m_stream.EndOfStream())
{
if (required)
Error("Incomplete MD5 file");
return false;
}
m_lineCount++;
m_currentLine = m_stream.ReadLine();
m_currentLine = m_currentLine.SubStringTo("//"); // On ignore les commentaires
m_currentLine.Simplify(); // Pour un traitement plus simple
}
while (m_currentLine.IsEmpty());
}
else
m_keepLastLine = false;
return true;
}
void MD5AnimParser::Error(const String& message)
{
NazaraError(message + " at line #" + String::Number(m_lineCount));
}
bool MD5AnimParser::ParseBaseframe()
{
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
Error("Joint count is invalid or missing");
return false;
}
for (unsigned int i = 0; i < jointCount; ++i)
{
if (!Advance())
return false;
if (std::sscanf(&m_currentLine[0], "( %f %f %f ) ( %f %f %f )", &m_joints[i].bindPos.x, &m_joints[i].bindPos.y, &m_joints[i].bindPos.z,
&m_joints[i].bindOrient.x, &m_joints[i].bindOrient.y, &m_joints[i].bindOrient.z) != 6)
{
UnrecognizedLine(true);
return false;
}
m_lineCount++;
m_currentLine = m_stream.ReadLine();
m_currentLine = m_currentLine.SubStringTo("//"); // On ignore les commentaires
m_currentLine.Simplify(); // Pour un traitement plus simple
}
while (m_currentLine.IsEmpty());
}
else
m_keepLastLine = false;
return true;
}
void NzMD5AnimParser::Error(const NzString& message)
{
NazaraError(message + " at line #" + NzString::Number(m_lineCount));
}
bool NzMD5AnimParser::ParseBaseframe()
{
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
Error("Joint count is invalid or missing");
return false;
}
for (unsigned int i = 0; i < jointCount; ++i)
{
if (!Advance())
return false;
if (std::sscanf(&m_currentLine[0], "( %f %f %f ) ( %f %f %f )", &m_joints[i].bindPos.x, &m_joints[i].bindPos.y, &m_joints[i].bindPos.z,
&m_joints[i].bindOrient.x, &m_joints[i].bindOrient.y, &m_joints[i].bindOrient.z) != 6)
if (m_currentLine != '}')
{
UnrecognizedLine(true);
return false;
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Bounds braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
return true;
}
if (!Advance())
return false;
if (m_currentLine != '}')
bool MD5AnimParser::ParseBounds()
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Bounds braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
return true;
}
bool NzMD5AnimParser::ParseBounds()
{
unsigned int frameCount = m_frames.size();
if (frameCount == 0)
{
Error("Frame count is invalid or missing");
return false;
}
for (unsigned int i = 0; i < frameCount; ++i)
{
if (!Advance())
return false;
NzVector3f min, max;
if (std::sscanf(&m_currentLine[0], "( %f %f %f ) ( %f %f %f )", &min.x, &min.y, &min.z, &max.x, &max.y, &max.z) != 6)
unsigned int frameCount = m_frames.size();
if (frameCount == 0)
{
UnrecognizedLine(true);
Error("Frame count is invalid or missing");
return false;
}
m_frames[i].bounds.Set(min, max);
}
if (!Advance())
return false;
if (m_currentLine != '}')
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Bounds braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
return true;
}
bool NzMD5AnimParser::ParseFrame()
{
unsigned int animatedComponentsCount = m_animatedComponents.size();
if (animatedComponentsCount == 0)
{
Error("Animated components count is missing or invalid");
return false;
}
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
Error("Joint count is invalid or missing");
return false;
}
NzString line;
unsigned int count = 0;
do
{
if (!Advance())
return false;
unsigned int index = 0;
unsigned int size = m_currentLine.GetSize();
do
for (unsigned int i = 0; i < frameCount; ++i)
{
float f;
int read;
if (std::sscanf(&m_currentLine[index], "%f%n", &f, &read) != 1)
if (!Advance())
return false;
Vector3f min, max;
if (std::sscanf(&m_currentLine[0], "( %f %f %f ) ( %f %f %f )", &min.x, &min.y, &min.z, &max.x, &max.y, &max.z) != 6)
{
UnrecognizedLine(true);
return false;
}
index += read;
m_animatedComponents[count] = f;
count++;
m_frames[i].bounds.Set(min, max);
}
while (index < size);
}
while (count < animatedComponentsCount);
m_frames[m_frameIndex].joints.resize(jointCount);
for (unsigned int i = 0; i < jointCount; ++i)
{
NzQuaternionf jointOrient = m_joints[i].bindOrient;
NzVector3f jointPos = m_joints[i].bindPos;
unsigned int j = 0;
if (m_joints[i].flags & 1) // Px
jointPos.x = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 2) // Py
jointPos.y = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 4) // Pz
jointPos.z = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 8) // Qx
jointOrient.x = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 16) // Qy
jointOrient.y = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 32) // Qz
jointOrient.z = m_animatedComponents[m_joints[i].index + j++];
jointOrient.ComputeW();
m_frames[m_frameIndex].joints[i].orient = jointOrient;
m_frames[m_frameIndex].joints[i].pos = jointPos;
}
if (!Advance(false))
return true;
if (m_currentLine != '}')
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Hierarchy braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
return true;
}
bool NzMD5AnimParser::ParseHierarchy()
{
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
Error("Joint count is invalid or missing");
return false;
}
for (unsigned int i = 0; i < jointCount; ++i)
{
if (!Advance())
return false;
unsigned int pos = m_currentLine.Find(' ');
if (pos == NzString::npos)
if (m_currentLine != '}')
{
UnrecognizedLine(true);
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Bounds braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
return true;
}
bool MD5AnimParser::ParseFrame()
{
unsigned int animatedComponentsCount = m_animatedComponents.size();
if (animatedComponentsCount == 0)
{
Error("Animated components count is missing or invalid");
return false;
}
if (pos >= 64)
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
NazaraError("Joint name is too long (>= 64 characters)");
Error("Joint count is invalid or missing");
return false;
}
char name[64];
if (std::sscanf(&m_currentLine[0], "%63s %d %u %u", &name[0], &m_joints[i].parent, &m_joints[i].flags, &m_joints[i].index) != 4)
{
UnrecognizedLine(true);
return false;
}
String line;
m_joints[i].name = name;
m_joints[i].name.Trim('"');
int parent = m_joints[i].parent;
if (parent >= 0)
unsigned int count = 0;
do
{
if (static_cast<unsigned int>(parent) >= jointCount)
if (!Advance())
return false;
unsigned int index = 0;
unsigned int size = m_currentLine.GetSize();
do
{
Error("Joint's parent is out of bounds (" + NzString::Number(parent) + " >= " + NzString::Number(jointCount) + ')');
float f;
int read;
if (std::sscanf(&m_currentLine[index], "%f%n", &f, &read) != 1)
{
UnrecognizedLine(true);
return false;
}
index += read;
m_animatedComponents[count] = f;
count++;
}
while (index < size);
}
while (count < animatedComponentsCount);
m_frames[m_frameIndex].joints.resize(jointCount);
for (unsigned int i = 0; i < jointCount; ++i)
{
Quaternionf jointOrient = m_joints[i].bindOrient;
Vector3f jointPos = m_joints[i].bindPos;
unsigned int j = 0;
if (m_joints[i].flags & 1) // Px
jointPos.x = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 2) // Py
jointPos.y = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 4) // Pz
jointPos.z = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 8) // Qx
jointOrient.x = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 16) // Qy
jointOrient.y = m_animatedComponents[m_joints[i].index + j++];
if (m_joints[i].flags & 32) // Qz
jointOrient.z = m_animatedComponents[m_joints[i].index + j++];
jointOrient.ComputeW();
m_frames[m_frameIndex].joints[i].orient = jointOrient;
m_frames[m_frameIndex].joints[i].pos = jointPos;
}
if (!Advance(false))
return true;
if (m_currentLine != '}')
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Hierarchy braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
return true;
}
bool MD5AnimParser::ParseHierarchy()
{
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
Error("Joint count is invalid or missing");
return false;
}
for (unsigned int i = 0; i < jointCount; ++i)
{
if (!Advance())
return false;
unsigned int pos = m_currentLine.Find(' ');
if (pos == String::npos)
{
UnrecognizedLine(true);
return false;
}
if (pos >= 64)
{
NazaraError("Joint name is too long (>= 64 characters)");
return false;
}
char name[64];
if (std::sscanf(&m_currentLine[0], "%63s %d %u %u", &name[0], &m_joints[i].parent, &m_joints[i].flags, &m_joints[i].index) != 4)
{
UnrecognizedLine(true);
return false;
}
m_joints[i].name = name;
m_joints[i].name.Trim('"');
int parent = m_joints[i].parent;
if (parent >= 0)
{
if (static_cast<unsigned int>(parent) >= jointCount)
{
Error("Joint's parent is out of bounds (" + String::Number(parent) + " >= " + String::Number(jointCount) + ')');
return false;
}
}
}
if (!Advance())
return false;
if (m_currentLine != '}')
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Hierarchy braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
return true;
}
if (!Advance())
return false;
if (m_currentLine != '}')
void MD5AnimParser::Warning(const String& message)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Hierarchy braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
NazaraWarning(message + " at line #" + String::Number(m_lineCount));
}
return true;
}
void MD5AnimParser::UnrecognizedLine(bool error)
{
String message = "Unrecognized \"" + m_currentLine + '"';
void NzMD5AnimParser::Warning(const NzString& message)
{
NazaraWarning(message + " at line #" + NzString::Number(m_lineCount));
}
void NzMD5AnimParser::UnrecognizedLine(bool error)
{
NzString message = "Unrecognized \"" + m_currentLine + '"';
if (error)
Error(message);
else
Warning(message);
if (error)
Error(message);
else
Warning(message);
}
}

View File

@@ -11,293 +11,299 @@
#include <memory>
#include <Nazara/Utility/Debug.hpp>
namespace
namespace Nz
{
bool IsSupported(const NzString& extension)
namespace
{
return (extension == "md5mesh");
}
nzTernary Check(NzInputStream& stream, const NzMeshParams& parameters)
{
NazaraUnused(parameters);
NzMD5MeshParser parser(stream);
return parser.Check();
}
bool Load(NzMesh* mesh, NzInputStream& stream, const NzMeshParams& parameters)
{
NzMD5MeshParser parser(stream);
if (!parser.Parse())
bool IsSupported(const String& extension)
{
NazaraError("MD5Mesh parser failed");
return false;
return (extension == "md5mesh");
}
// Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette
NzQuaternionf rotationQuat = NzQuaternionf::RotationBetween(NzVector3f::UnitX(), NzVector3f::Forward()) *
NzQuaternionf::RotationBetween(NzVector3f::UnitZ(), NzVector3f::Up());
NzString baseDir = stream.GetDirectory();
// Le hellknight de Doom 3 fait ~120 unités, et il est dit qu'il fait trois mètres
// Nous réduisons donc la taille générale des fichiers MD5 de 1/40
NzVector3f scale(parameters.scale/40.f);
const NzMD5MeshParser::Joint* joints = parser.GetJoints();
const NzMD5MeshParser::Mesh* meshes = parser.GetMeshes();
unsigned int jointCount = parser.GetJointCount();
unsigned int meshCount = parser.GetMeshCount();
if (parameters.animated)
Ternary Check(InputStream& stream, const MeshParams& parameters)
{
mesh->CreateSkeletal(jointCount);
NazaraUnused(parameters);
NzSkeleton* skeleton = mesh->GetSkeleton();
for (unsigned int i = 0; i < jointCount; ++i)
{
NzJoint* joint = skeleton->GetJoint(i);
int parent = joints[i].parent;
if (parent >= 0)
joint->SetParent(skeleton->GetJoint(parent));
joint->SetName(joints[i].name);
NzMatrix4f bindMatrix;
if (parent >= 0)
bindMatrix.MakeTransform(joints[i].bindPos, joints[i].bindOrient);
else
bindMatrix.MakeTransform(rotationQuat * joints[i].bindPos, rotationQuat * joints[i].bindOrient);
joint->SetInverseBindMatrix(bindMatrix.InverseAffine());
}
mesh->SetMaterialCount(meshCount);
for (unsigned int i = 0; i < meshCount; ++i)
{
const NzMD5MeshParser::Mesh& md5Mesh = meshes[i];
unsigned int indexCount = md5Mesh.triangles.size()*3;
unsigned int vertexCount = md5Mesh.vertices.size();
bool largeIndices = (vertexCount > std::numeric_limits<nzUInt16>::max());
NzIndexBufferRef indexBuffer = NzIndexBuffer::New(largeIndices, indexCount, parameters.storage);
NzVertexBufferRef vertexBuffer = NzVertexBuffer::New(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent_Skinning), vertexCount, parameters.storage, nzBufferUsage_Static);
// Index buffer
NzIndexMapper indexMapper(indexBuffer, nzBufferAccess_DiscardAndWrite);
// Le format définit un set de triangles nous permettant de retrouver facilement les indices
// Cependant les sommets des triangles ne sont pas spécifiés dans le même ordre que ceux du moteur
// (On parle ici de winding)
unsigned int index = 0;
for (const NzMD5MeshParser::Triangle& triangle : md5Mesh.triangles)
{
// On les respécifie dans le bon ordre (inversion du winding)
indexMapper.Set(index++, triangle.x);
indexMapper.Set(index++, triangle.z);
indexMapper.Set(index++, triangle.y);
}
indexMapper.Unmap();
if (parameters.optimizeIndexBuffers)
indexBuffer->Optimize();
// Vertex buffer
struct Weight
{
float bias;
unsigned int jointIndex;
};
std::vector<Weight> tempWeights;
NzBufferMapper<NzVertexBuffer> vertexMapper(vertexBuffer, nzBufferAccess_WriteOnly);
NzSkeletalMeshVertex* vertices = static_cast<NzSkeletalMeshVertex*>(vertexMapper.GetPointer());
for (const NzMD5MeshParser::Vertex& vertex : md5Mesh.vertices)
{
// Skinning MD5 (Formule d'Id Tech)
NzVector3f finalPos(NzVector3f::Zero());
// On stocke tous les poids dans le tableau temporaire en même temps qu'on calcule la position finale du sommet.
tempWeights.resize(vertex.weightCount);
for (unsigned int j = 0; j < vertex.weightCount; ++j)
{
const NzMD5MeshParser::Weight& weight = md5Mesh.weights[vertex.startWeight + j];
const NzMD5MeshParser::Joint& joint = joints[weight.joint];
finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias;
// Avant d'ajouter les poids, il faut s'assurer qu'il n'y en ait pas plus que le maximum supporté
// et dans le cas contraire, garder les poids les plus importants et les renormaliser
tempWeights[j] = {weight.bias, weight.joint};
}
// Avons nous plus de poids que le moteur ne peut en supporter ?
unsigned int weightCount = vertex.weightCount;
if (weightCount > NAZARA_UTILITY_SKINNING_MAX_WEIGHTS)
{
// Pour augmenter la qualité du skinning tout en ne gardant que X poids, on ne garde que les poids
// les plus importants, ayant le plus d'impact sur le sommet final
std::sort(tempWeights.begin(), tempWeights.end(), [] (const Weight& a, const Weight& b) -> bool {
return a.bias > b.bias;
});
// Sans oublier bien sûr de renormaliser les poids (que leur somme soit 1)
float weightSum = 0.f;
for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j)
weightSum += tempWeights[j].bias;
for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j)
tempWeights[j].bias /= weightSum;
weightCount = NAZARA_UTILITY_SKINNING_MAX_WEIGHTS;
}
vertices->weightCount = weightCount;
for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j)
{
if (j < weightCount)
{
// On donne une valeur aux poids présents
vertices->weights[j] = tempWeights[j].bias;
vertices->jointIndexes[j] = tempWeights[j].jointIndex;
}
else
{
// Et un poids de 0 sur le joint 0 pour les autres (nécessaire pour le GPU Skinning)
// La raison est que le GPU ne tiendra pas compte du nombre de poids pour des raisons de performances.
vertices->weights[j] = 0.f;
vertices->jointIndexes[j] = 0;
}
}
vertices->position = finalPos;
vertices->uv.Set(vertex.uv.x, (parameters.flipUVs) ? 1.f - vertex.uv.y : vertex.uv.y); // Inversion des UV si demandé
vertices++;
}
vertexMapper.Unmap();
// Material
mesh->SetMaterial(i, baseDir + md5Mesh.shader);
// Submesh
NzSkeletalMeshRef subMesh = NzSkeletalMesh::New(mesh);
subMesh->Create(vertexBuffer);
subMesh->SetIndexBuffer(indexBuffer);
subMesh->GenerateNormalsAndTangents();
subMesh->SetMaterialIndex(i);
subMesh->SetPrimitiveMode(nzPrimitiveMode_TriangleList);
mesh->AddSubMesh(subMesh);
// Animation
// Il est peut-être éventuellement possible que la probabilité que l'animation ait le même nom soit non-nulle.
NzString path = stream.GetPath();
if (!path.IsEmpty())
{
path.Replace(".md5mesh", ".md5anim", -8, NzString::CaseInsensitive);
if (NzFile::Exists(path))
mesh->SetAnimation(path);
}
}
MD5MeshParser parser(stream);
return parser.Check();
}
else
bool Load(Mesh* mesh, InputStream& stream, const MeshParams& parameters)
{
if (!mesh->CreateStatic()) // Ne devrait jamais échouer
MD5MeshParser parser(stream);
if (!parser.Parse())
{
NazaraInternalError("Failed to create mesh");
NazaraError("MD5Mesh parser failed");
return false;
}
mesh->SetMaterialCount(meshCount);
for (unsigned int i = 0; i < meshCount; ++i)
// Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette
Quaternionf rotationQuat = Quaternionf::RotationBetween(Vector3f::UnitX(), Vector3f::Forward()) *
Quaternionf::RotationBetween(Vector3f::UnitZ(), Vector3f::Up());
String baseDir = stream.GetDirectory();
// Le hellknight de Doom 3 fait ~120 unités, et il est dit qu'il fait trois mètres
// Nous réduisons donc la taille générale des fichiers MD5 de 1/40
Vector3f scale(parameters.scale/40.f);
const MD5MeshParser::Joint* joints = parser.GetJoints();
const MD5MeshParser::Mesh* meshes = parser.GetMeshes();
unsigned int jointCount = parser.GetJointCount();
unsigned int meshCount = parser.GetMeshCount();
if (parameters.animated)
{
const NzMD5MeshParser::Mesh& md5Mesh = meshes[i];
unsigned int indexCount = md5Mesh.triangles.size()*3;
unsigned int vertexCount = md5Mesh.vertices.size();
mesh->CreateSkeletal(jointCount);
// Index buffer
bool largeIndices = (vertexCount > std::numeric_limits<nzUInt16>::max());
NzIndexBufferRef indexBuffer = NzIndexBuffer::New(largeIndices, indexCount, parameters.storage);
NzIndexMapper indexMapper(indexBuffer, nzBufferAccess_DiscardAndWrite);
NzIndexIterator index = indexMapper.begin();
for (const NzMD5MeshParser::Triangle& triangle : md5Mesh.triangles)
Skeleton* skeleton = mesh->GetSkeleton();
for (unsigned int i = 0; i < jointCount; ++i)
{
// On les respécifie dans le bon ordre
*index++ = triangle.x;
*index++ = triangle.z;
*index++ = triangle.y;
Joint* joint = skeleton->GetJoint(i);
int parent = joints[i].parent;
if (parent >= 0)
joint->SetParent(skeleton->GetJoint(parent));
joint->SetName(joints[i].name);
Matrix4f bindMatrix;
if (parent >= 0)
bindMatrix.MakeTransform(joints[i].bindPos, joints[i].bindOrient);
else
bindMatrix.MakeTransform(rotationQuat * joints[i].bindPos, rotationQuat * joints[i].bindOrient);
joint->SetInverseBindMatrix(bindMatrix.InverseAffine());
}
indexMapper.Unmap();
// Vertex buffer
NzVertexBufferRef vertexBuffer = NzVertexBuffer::New(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent), vertexCount, parameters.storage);
NzBufferMapper<NzVertexBuffer> vertexMapper(vertexBuffer, nzBufferAccess_WriteOnly);
NzMeshVertex* vertex = reinterpret_cast<NzMeshVertex*>(vertexMapper.GetPointer());
for (const NzMD5MeshParser::Vertex& md5Vertex : md5Mesh.vertices)
mesh->SetMaterialCount(meshCount);
for (unsigned int i = 0; i < meshCount; ++i)
{
// Skinning MD5 (Formule d'Id Tech)
NzVector3f finalPos(NzVector3f::Zero());
for (unsigned int j = 0; j < md5Vertex.weightCount; ++j)
{
const NzMD5MeshParser::Weight& weight = md5Mesh.weights[md5Vertex.startWeight + j];
const NzMD5MeshParser::Joint& joint = joints[weight.joint];
const MD5MeshParser::Mesh& md5Mesh = meshes[i];
finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias;
unsigned int indexCount = md5Mesh.triangles.size()*3;
unsigned int vertexCount = md5Mesh.vertices.size();
bool largeIndices = (vertexCount > std::numeric_limits<UInt16>::max());
IndexBufferRef indexBuffer = IndexBuffer::New(largeIndices, indexCount, parameters.storage);
VertexBufferRef vertexBuffer = VertexBuffer::New(VertexDeclaration::Get(VertexLayout_XYZ_Normal_UV_Tangent_Skinning), vertexCount, parameters.storage, BufferUsage_Static);
// Index buffer
IndexMapper indexMapper(indexBuffer, BufferAccess_DiscardAndWrite);
// Le format définit un set de triangles nous permettant de retrouver facilement les indices
// Cependant les sommets des triangles ne sont pas spécifiés dans le même ordre que ceux du moteur
// (On parle ici de winding)
unsigned int index = 0;
for (const MD5MeshParser::Triangle& triangle : md5Mesh.triangles)
{
// On les respécifie dans le bon ordre (inversion du winding)
indexMapper.Set(index++, triangle.x);
indexMapper.Set(index++, triangle.z);
indexMapper.Set(index++, triangle.y);
}
// On retourne le modèle dans le bon sens
vertex->position = scale * (rotationQuat * finalPos);
vertex->uv.Set(md5Vertex.uv.x, (parameters.flipUVs) ? 1.f - md5Vertex.uv.y : md5Vertex.uv.y); // Inversion des UV si demandé
vertex++;
indexMapper.Unmap();
if (parameters.optimizeIndexBuffers)
indexBuffer->Optimize();
// Vertex buffer
struct Weight
{
float bias;
unsigned int jointIndex;
};
std::vector<Weight> tempWeights;
BufferMapper<VertexBuffer> vertexMapper(vertexBuffer, BufferAccess_WriteOnly);
SkeletalMeshVertex* vertices = static_cast<SkeletalMeshVertex*>(vertexMapper.GetPointer());
for (const MD5MeshParser::Vertex& vertex : md5Mesh.vertices)
{
// Skinning MD5 (Formule d'Id Tech)
Vector3f finalPos(Vector3f::Zero());
// On stocke tous les poids dans le tableau temporaire en même temps qu'on calcule la position finale du sommet.
tempWeights.resize(vertex.weightCount);
for (unsigned int j = 0; j < vertex.weightCount; ++j)
{
const MD5MeshParser::Weight& weight = md5Mesh.weights[vertex.startWeight + j];
const MD5MeshParser::Joint& joint = joints[weight.joint];
finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias;
// Avant d'ajouter les poids, il faut s'assurer qu'il n'y en ait pas plus que le maximum supporté
// et dans le cas contraire, garder les poids les plus importants et les renormaliser
tempWeights[j] = {weight.bias, weight.joint};
}
// Avons nous plus de poids que le moteur ne peut en supporter ?
unsigned int weightCount = vertex.weightCount;
if (weightCount > NAZARA_UTILITY_SKINNING_MAX_WEIGHTS)
{
// Pour augmenter la qualité du skinning tout en ne gardant que X poids, on ne garde que les poids
// les plus importants, ayant le plus d'impact sur le sommet final
std::sort(tempWeights.begin(), tempWeights.end(), [] (const Weight& a, const Weight& b) -> bool {
return a.bias > b.bias;
});
// Sans oublier bien sûr de renormaliser les poids (que leur somme soit 1)
float weightSum = 0.f;
for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j)
weightSum += tempWeights[j].bias;
for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j)
tempWeights[j].bias /= weightSum;
weightCount = NAZARA_UTILITY_SKINNING_MAX_WEIGHTS;
}
vertices->weightCount = weightCount;
for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j)
{
if (j < weightCount)
{
// On donne une valeur aux poids présents
vertices->weights[j] = tempWeights[j].bias;
vertices->jointIndexes[j] = tempWeights[j].jointIndex;
}
else
{
// Et un poids de 0 sur le joint 0 pour les autres (nécessaire pour le GPU Skinning)
// La raison est que le GPU ne tiendra pas compte du nombre de poids pour des raisons de performances.
vertices->weights[j] = 0.f;
vertices->jointIndexes[j] = 0;
}
}
vertices->position = finalPos;
vertices->uv.Set(vertex.uv.x, (parameters.flipUVs) ? 1.f - vertex.uv.y : vertex.uv.y); // Inversion des UV si demandé
vertices++;
}
vertexMapper.Unmap();
// Material
mesh->SetMaterial(i, baseDir + md5Mesh.shader);
// Submesh
SkeletalMeshRef subMesh = SkeletalMesh::New(mesh);
subMesh->Create(vertexBuffer);
subMesh->SetIndexBuffer(indexBuffer);
subMesh->GenerateNormalsAndTangents();
subMesh->SetMaterialIndex(i);
subMesh->SetPrimitiveMode(PrimitiveMode_TriangleList);
mesh->AddSubMesh(subMesh);
// Animation
// Il est peut-être éventuellement possible que la probabilité que l'animation ait le même nom soit non-nulle.
String path = stream.GetPath();
if (!path.IsEmpty())
{
path.Replace(".md5mesh", ".md5anim", -8, String::CaseInsensitive);
if (File::Exists(path))
mesh->SetAnimation(path);
}
}
}
else
{
if (!mesh->CreateStatic()) // Ne devrait jamais échouer
{
NazaraInternalError("Failed to create mesh");
return false;
}
vertexMapper.Unmap();
mesh->SetMaterialCount(meshCount);
for (unsigned int i = 0; i < meshCount; ++i)
{
const MD5MeshParser::Mesh& md5Mesh = meshes[i];
unsigned int indexCount = md5Mesh.triangles.size()*3;
unsigned int vertexCount = md5Mesh.vertices.size();
// Submesh
NzStaticMeshRef subMesh = NzStaticMesh::New(mesh);
subMesh->Create(vertexBuffer);
// Index buffer
bool largeIndices = (vertexCount > std::numeric_limits<UInt16>::max());
if (parameters.optimizeIndexBuffers)
indexBuffer->Optimize();
IndexBufferRef indexBuffer = IndexBuffer::New(largeIndices, indexCount, parameters.storage);
subMesh->SetIndexBuffer(indexBuffer);
subMesh->GenerateAABB();
subMesh->GenerateNormalsAndTangents();
subMesh->SetMaterialIndex(i);
IndexMapper indexMapper(indexBuffer, BufferAccess_DiscardAndWrite);
IndexIterator index = indexMapper.begin();
mesh->AddSubMesh(subMesh);
for (const MD5MeshParser::Triangle& triangle : md5Mesh.triangles)
{
// On les respécifie dans le bon ordre
*index++ = triangle.x;
*index++ = triangle.z;
*index++ = triangle.y;
}
indexMapper.Unmap();
// Material
mesh->SetMaterial(i, baseDir + md5Mesh.shader);
// Vertex buffer
VertexBufferRef vertexBuffer = VertexBuffer::New(VertexDeclaration::Get(VertexLayout_XYZ_Normal_UV_Tangent), vertexCount, parameters.storage);
BufferMapper<VertexBuffer> vertexMapper(vertexBuffer, BufferAccess_WriteOnly);
MeshVertex* vertex = reinterpret_cast<MeshVertex*>(vertexMapper.GetPointer());
for (const MD5MeshParser::Vertex& md5Vertex : md5Mesh.vertices)
{
// Skinning MD5 (Formule d'Id Tech)
Vector3f finalPos(Vector3f::Zero());
for (unsigned int j = 0; j < md5Vertex.weightCount; ++j)
{
const MD5MeshParser::Weight& weight = md5Mesh.weights[md5Vertex.startWeight + j];
const MD5MeshParser::Joint& joint = joints[weight.joint];
finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias;
}
// On retourne le modèle dans le bon sens
vertex->position = scale * (rotationQuat * finalPos);
vertex->uv.Set(md5Vertex.uv.x, (parameters.flipUVs) ? 1.f - md5Vertex.uv.y : md5Vertex.uv.y); // Inversion des UV si demandé
vertex++;
}
vertexMapper.Unmap();
// Submesh
StaticMeshRef subMesh = StaticMesh::New(mesh);
subMesh->Create(vertexBuffer);
if (parameters.optimizeIndexBuffers)
indexBuffer->Optimize();
subMesh->SetIndexBuffer(indexBuffer);
subMesh->GenerateAABB();
subMesh->GenerateNormalsAndTangents();
subMesh->SetMaterialIndex(i);
mesh->AddSubMesh(subMesh);
// Material
mesh->SetMaterial(i, baseDir + md5Mesh.shader);
}
if (parameters.center)
mesh->Recenter();
}
if (parameters.center)
mesh->Recenter();
return true;
}
}
namespace Loaders
{
void RegisterMD5Mesh()
{
MeshLoader::RegisterLoader(IsSupported, Check, Load);
}
return true;
void UnregisterMD5Mesh()
{
MeshLoader::UnregisterLoader(IsSupported, Check, Load);
}
}
}
void NzLoaders_MD5Mesh_Register()
{
NzMeshLoader::RegisterLoader(IsSupported, Check, Load);
}
void NzLoaders_MD5Mesh_Unregister()
{
NzMeshLoader::UnregisterLoader(IsSupported, Check, Load);
}

View File

@@ -9,7 +9,13 @@
#include <Nazara/Prerequesites.hpp>
void NzLoaders_MD5Mesh_Register();
void NzLoaders_MD5Mesh_Unregister();
namespace Nz
{
namespace Loaders
{
void RegisterMD5Mesh();
void UnregisterMD5Mesh();
}
}
#endif // NAZARA_LOADERS_MD5MESH_HPP

View File

@@ -19,410 +19,413 @@
#include <memory>
#include <Nazara/Utility/Debug.hpp>
NzMD5MeshParser::NzMD5MeshParser(NzInputStream& stream) :
m_stream(stream),
m_keepLastLine(false),
m_lineCount(0),
m_meshIndex(0),
m_streamFlags(stream.GetStreamOptions())
namespace Nz
{
if ((m_streamFlags & nzStreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags | nzStreamOption_Text);
}
NzMD5MeshParser::~NzMD5MeshParser()
{
if ((m_streamFlags & nzStreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags);
}
nzTernary NzMD5MeshParser::Check()
{
if (Advance(false))
MD5MeshParser::MD5MeshParser(InputStream& stream) :
m_stream(stream),
m_keepLastLine(false),
m_lineCount(0),
m_meshIndex(0),
m_streamFlags(stream.GetStreamOptions())
{
unsigned int version;
if (std::sscanf(&m_currentLine[0], " MD5Version %u", &version) == 1)
{
if (version == 10)
return nzTernary_True;
}
if ((m_streamFlags & StreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags | StreamOption_Text);
}
return nzTernary_False;
}
const NzMD5MeshParser::Joint* NzMD5MeshParser::GetJoints() const
{
return m_joints.data();
}
unsigned int NzMD5MeshParser::GetJointCount() const
{
return m_joints.size();
}
const NzMD5MeshParser::Mesh* NzMD5MeshParser::GetMeshes() const
{
return m_meshes.data();
}
unsigned int NzMD5MeshParser::GetMeshCount() const
{
return m_meshes.size();
}
bool NzMD5MeshParser::Parse()
{
while (Advance(false))
MD5MeshParser::~MD5MeshParser()
{
switch (m_currentLine[0])
if ((m_streamFlags & StreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags);
}
Ternary MD5MeshParser::Check()
{
if (Advance(false))
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
case 'M': // MD5Version
if (m_currentLine.GetWord(0) != "MD5Version")
UnrecognizedLine();
break;
unsigned int version;
if (std::sscanf(&m_currentLine[0], " MD5Version %u", &version) == 1)
{
if (version == 10)
return Ternary_True;
}
}
case 'c': // commandline
if (m_currentLine.GetWord(0) != "commandline")
UnrecognizedLine();
break;
#endif
return Ternary_False;
}
case 'j': // joints
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_currentLine.StartsWith("joints {"))
{
UnrecognizedLine();
break;
}
#endif
const MD5MeshParser::Joint* MD5MeshParser::GetJoints() const
{
return m_joints.data();
}
if (!ParseJoints())
{
Error("Failed to parse joints");
return false;
}
break;
unsigned int MD5MeshParser::GetJointCount() const
{
return m_joints.size();
}
case 'm': // mesh
const MD5MeshParser::Mesh* MD5MeshParser::GetMeshes() const
{
return m_meshes.data();
}
unsigned int MD5MeshParser::GetMeshCount() const
{
return m_meshes.size();
}
bool MD5MeshParser::Parse()
{
while (Advance(false))
{
switch (m_currentLine[0])
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (m_currentLine != "mesh {")
{
UnrecognizedLine();
case 'M': // MD5Version
if (m_currentLine.GetWord(0) != "MD5Version")
UnrecognizedLine();
break;
case 'c': // commandline
if (m_currentLine.GetWord(0) != "commandline")
UnrecognizedLine();
break;
}
#endif
if (m_meshIndex >= m_meshes.size())
{
case 'j': // joints
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("More meshes than registred");
if (!m_currentLine.StartsWith("joints {"))
{
UnrecognizedLine();
break;
}
#endif
m_meshes.push_back(Mesh());
if (!ParseJoints())
{
Error("Failed to parse joints");
return false;
}
break;
case 'm': // mesh
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (m_currentLine != "mesh {")
{
UnrecognizedLine();
break;
}
#endif
if (m_meshIndex >= m_meshes.size())
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("More meshes than registred");
#endif
m_meshes.push_back(Mesh());
}
if (!ParseMesh())
{
NazaraError("Failed to parse mesh");
return false;
}
m_meshIndex++;
break;
}
if (!ParseMesh())
case 'n': // num[Frames/Joints]
{
NazaraError("Failed to parse mesh");
unsigned int count;
if (std::sscanf(&m_currentLine[0], "numJoints %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_joints.empty())
Warning("Joint count is already defined");
#endif
m_joints.resize(count);
}
else if (std::sscanf(&m_currentLine[0], "numMeshes %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_meshes.empty())
Warning("Mesh count is already defined");
#endif
m_meshes.resize(count);
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
}
default:
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
}
return true;
}
bool MD5MeshParser::Advance(bool required)
{
if (!m_keepLastLine)
{
do
{
if (m_stream.EndOfStream())
{
if (required)
Error("Incomplete MD5 file");
return false;
}
m_meshIndex++;
break;
m_lineCount++;
m_currentLine = m_stream.ReadLine();
m_currentLine = m_currentLine.SubStringTo("//"); // On ignore les commentaires
m_currentLine.Simplify(); // Pour un traitement plus simple
}
case 'n': // num[Frames/Joints]
{
unsigned int count;
if (std::sscanf(&m_currentLine[0], "numJoints %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_joints.empty())
Warning("Joint count is already defined");
#endif
m_joints.resize(count);
}
else if (std::sscanf(&m_currentLine[0], "numMeshes %u", &count) == 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_meshes.empty())
Warning("Mesh count is already defined");
#endif
m_meshes.resize(count);
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
}
default:
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
while (m_currentLine.IsEmpty());
}
else
m_keepLastLine = false;
return true;
}
return true;
}
bool NzMD5MeshParser::Advance(bool required)
{
if (!m_keepLastLine)
void MD5MeshParser::Error(const String& message)
{
do
{
if (m_stream.EndOfStream())
{
if (required)
Error("Incomplete MD5 file");
NazaraError(message + " at line #" + String::Number(m_lineCount));
}
bool MD5MeshParser::ParseJoints()
{
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
Error("Joint count is invalid or missing");
return false;
}
for (unsigned int i = 0; i < jointCount; ++i)
{
if (!Advance())
return false;
unsigned int pos = m_currentLine.Find(' ');
if (pos == String::npos)
{
UnrecognizedLine(true);
return false;
}
m_lineCount++;
if (pos >= 64)
{
NazaraError("Joint name is too long (>= 64 characters)");
return false;
}
m_currentLine = m_stream.ReadLine();
m_currentLine = m_currentLine.SubStringTo("//"); // On ignore les commentaires
m_currentLine.Simplify(); // Pour un traitement plus simple
char name[64];
if (std::sscanf(&m_currentLine[0], "%63s %d ( %f %f %f ) ( %f %f %f )", &name[0], &m_joints[i].parent,
&m_joints[i].bindPos.x, &m_joints[i].bindPos.y, &m_joints[i].bindPos.z,
&m_joints[i].bindOrient.x, &m_joints[i].bindOrient.y, &m_joints[i].bindOrient.z) != 8)
{
UnrecognizedLine(true);
return false;
}
m_joints[i].name = name;
m_joints[i].name.Trim('"');
int parent = m_joints[i].parent;
if (parent >= 0)
{
if (static_cast<unsigned int>(parent) >= jointCount)
{
Error("Joint's parent is out of bounds (" + String::Number(parent) + " >= " + String::Number(jointCount) + ')');
return false;
}
}
m_joints[i].bindOrient.ComputeW(); // On calcule la composante W
}
while (m_currentLine.IsEmpty());
}
else
m_keepLastLine = false;
return true;
}
void NzMD5MeshParser::Error(const NzString& message)
{
NazaraError(message + " at line #" + NzString::Number(m_lineCount));
}
bool NzMD5MeshParser::ParseJoints()
{
unsigned int jointCount = m_joints.size();
if (jointCount == 0)
{
Error("Joint count is invalid or missing");
return false;
}
for (unsigned int i = 0; i < jointCount; ++i)
{
if (!Advance())
return false;
unsigned int pos = m_currentLine.Find(' ');
if (pos == NzString::npos)
if (m_currentLine != '}')
{
UnrecognizedLine(true);
return false;
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Hierarchy braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
if (pos >= 64)
{
NazaraError("Joint name is too long (>= 64 characters)");
return false;
}
return true;
}
char name[64];
if (std::sscanf(&m_currentLine[0], "%63s %d ( %f %f %f ) ( %f %f %f )", &name[0], &m_joints[i].parent,
&m_joints[i].bindPos.x, &m_joints[i].bindPos.y, &m_joints[i].bindPos.z,
&m_joints[i].bindOrient.x, &m_joints[i].bindOrient.y, &m_joints[i].bindOrient.z) != 8)
bool MD5MeshParser::ParseMesh()
{
bool finished = false;
while (!finished && Advance(false))
{
UnrecognizedLine(true);
return false;
}
m_joints[i].name = name;
m_joints[i].name.Trim('"');
int parent = m_joints[i].parent;
if (parent >= 0)
{
if (static_cast<unsigned int>(parent) >= jointCount)
switch (m_currentLine[0])
{
Error("Joint's parent is out of bounds (" + NzString::Number(parent) + " >= " + NzString::Number(jointCount) + ')');
return false;
}
}
case '}':
finished = true;
break;
m_joints[i].bindOrient.ComputeW(); // On calcule la composante W
}
case 's': // shader
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_currentLine.StartsWith("shader "))
{
UnrecognizedLine();
break;
}
#endif
if (!Advance())
return false;
m_meshes[m_meshIndex].shader = m_currentLine.SubString(7);
m_meshes[m_meshIndex].shader.Trim('"');
break;
if (m_currentLine != '}')
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
Warning("Hierarchy braces closing not found");
#endif
// On tente de survivre à l'erreur
m_keepLastLine = true;
}
return true;
}
bool NzMD5MeshParser::ParseMesh()
{
bool finished = false;
while (!finished && Advance(false))
{
switch (m_currentLine[0])
{
case '}':
finished = true;
break;
case 's': // shader
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!m_currentLine.StartsWith("shader "))
case 'n': // num[tris/verts]
{
UnrecognizedLine();
unsigned int count;
if (std::sscanf(&m_currentLine[0], "numtris %u", &count) == 1)
{
m_meshes[m_meshIndex].triangles.resize(count);
for (unsigned int i = 0; i < count; ++i)
{
if (!Advance())
return false;
Triangle& triangle = m_meshes[m_meshIndex].triangles[i];
unsigned int index;
if (std::sscanf(&m_currentLine[0], "tri %u %u %u %u", &index, &triangle.x, &triangle.y, &triangle.z) != 4)
{
UnrecognizedLine(true);
return false;
}
if (index != i)
{
Error("Unexpected triangle index (expected " + String::Number(i) + ", got " + String::Number(index) + ')');
return false;
}
}
}
else if (std::sscanf(&m_currentLine[0], "numverts %u", &count) == 1)
{
m_meshes[m_meshIndex].vertices.resize(count);
for (unsigned int i = 0; i < count; ++i)
{
if (!Advance())
return false;
Vertex& vertex = m_meshes[m_meshIndex].vertices[i];
unsigned int index;
if (std::sscanf(&m_currentLine[0], "vert %u ( %f %f ) %u %u", &index, &vertex.uv.x, &vertex.uv.y, &vertex.startWeight, &vertex.weightCount) != 5)
{
UnrecognizedLine(true);
return false;
}
if (index != i)
{
Error("Unexpected vertex index (expected " + String::Number(i) + ", got " + String::Number(index) + ')');
return false;
}
}
}
else if (std::sscanf(&m_currentLine[0], "numweights %u", &count) == 1)
{
m_meshes[m_meshIndex].weights.resize(count);
for (unsigned int i = 0; i < count; ++i)
{
if (!Advance())
return false;
Weight& weight = m_meshes[m_meshIndex].weights[i];
unsigned int index;
if (std::sscanf(&m_currentLine[0], "weight %u %u %f ( %f %f %f )", &index, &weight.joint, &weight.bias,
&weight.pos.x, &weight.pos.y, &weight.pos.z) != 6)
{
UnrecognizedLine(true);
return false;
}
if (index != i)
{
Error("Unexpected weight index (expected " + String::Number(i) + ", got " + String::Number(index) + ')');
return false;
}
}
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
UnrecognizedLine();
#endif
break;
}
#endif
m_meshes[m_meshIndex].shader = m_currentLine.SubString(7);
m_meshes[m_meshIndex].shader.Trim('"');
break;
case 'n': // num[tris/verts]
{
unsigned int count;
if (std::sscanf(&m_currentLine[0], "numtris %u", &count) == 1)
{
m_meshes[m_meshIndex].triangles.resize(count);
for (unsigned int i = 0; i < count; ++i)
{
if (!Advance())
return false;
Triangle& triangle = m_meshes[m_meshIndex].triangles[i];
unsigned int index;
if (std::sscanf(&m_currentLine[0], "tri %u %u %u %u", &index, &triangle.x, &triangle.y, &triangle.z) != 4)
{
UnrecognizedLine(true);
return false;
}
if (index != i)
{
Error("Unexpected triangle index (expected " + NzString::Number(i) + ", got " + NzString::Number(index) + ')');
return false;
}
}
}
else if (std::sscanf(&m_currentLine[0], "numverts %u", &count) == 1)
{
m_meshes[m_meshIndex].vertices.resize(count);
for (unsigned int i = 0; i < count; ++i)
{
if (!Advance())
return false;
Vertex& vertex = m_meshes[m_meshIndex].vertices[i];
unsigned int index;
if (std::sscanf(&m_currentLine[0], "vert %u ( %f %f ) %u %u", &index, &vertex.uv.x, &vertex.uv.y, &vertex.startWeight, &vertex.weightCount) != 5)
{
UnrecognizedLine(true);
return false;
}
if (index != i)
{
Error("Unexpected vertex index (expected " + NzString::Number(i) + ", got " + NzString::Number(index) + ')');
return false;
}
}
}
else if (std::sscanf(&m_currentLine[0], "numweights %u", &count) == 1)
{
m_meshes[m_meshIndex].weights.resize(count);
for (unsigned int i = 0; i < count; ++i)
{
if (!Advance())
return false;
Weight& weight = m_meshes[m_meshIndex].weights[i];
unsigned int index;
if (std::sscanf(&m_currentLine[0], "weight %u %u %f ( %f %f %f )", &index, &weight.joint, &weight.bias,
&weight.pos.x, &weight.pos.y, &weight.pos.z) != 6)
{
UnrecognizedLine(true);
return false;
}
if (index != i)
{
Error("Unexpected weight index (expected " + NzString::Number(i) + ", got " + NzString::Number(index) + ')');
return false;
}
}
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
else
default:
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
#endif
break;
}
default:
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
if (m_meshes[m_meshIndex].triangles.empty())
{
NazaraError("Mesh has no triangles");
return false;
}
if (m_meshes[m_meshIndex].vertices.empty())
{
NazaraError("Mesh has no vertices");
return false;
}
if (m_meshes[m_meshIndex].weights.empty())
{
NazaraError("Mesh has no weights");
return false;
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!finished)
Warning("Mesh braces closing not found");
#endif
return true;
}
if (m_meshes[m_meshIndex].triangles.empty())
void MD5MeshParser::Warning(const String& message)
{
NazaraError("Mesh has no triangles");
return false;
NazaraWarning(message + " at line #" + String::Number(m_lineCount));
}
if (m_meshes[m_meshIndex].vertices.empty())
void MD5MeshParser::UnrecognizedLine(bool error)
{
NazaraError("Mesh has no vertices");
return false;
}
String message = "Unrecognized \"" + m_currentLine + '"';
if (m_meshes[m_meshIndex].weights.empty())
{
NazaraError("Mesh has no weights");
return false;
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (!finished)
Warning("Mesh braces closing not found");
#endif
return true;
if (error)
Error(message);
else
Warning(message);
}
void NzMD5MeshParser::Warning(const NzString& message)
{
NazaraWarning(message + " at line #" + NzString::Number(m_lineCount));
}
void NzMD5MeshParser::UnrecognizedLine(bool error)
{
NzString message = "Unrecognized \"" + m_currentLine + '"';
if (error)
Error(message);
else
Warning(message);
}

View File

@@ -10,324 +10,327 @@
#include <memory>
#include <Nazara/Utility/Debug.hpp>
NzMTLParser::NzMTLParser(NzInputStream& stream) :
m_stream(stream),
m_streamFlags(stream.GetStreamOptions())
namespace Nz
{
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;
}
const std::unordered_map<NzString, NzMTLParser::Material>& NzMTLParser::GetMaterials() const
{
return m_materials;
}
bool NzMTLParser::Parse()
{
m_keepLastLine = false;
m_lineCount = 0;
m_materials.clear();
Material* currentMaterial = nullptr;
while (Advance(false))
MTLParser::MTLParser(InputStream& stream) :
m_stream(stream),
m_streamFlags(stream.GetStreamOptions())
{
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"];
if ((m_streamFlags & StreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags | StreamOption_Text);
}
currentMaterial->ambient = NzColor(static_cast<nzUInt8>(r*255.f), static_cast<nzUInt8>(g*255.f), static_cast<nzUInt8>(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"];
MTLParser::~MTLParser()
{
if ((m_streamFlags & StreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags);
}
currentMaterial->diffuse = NzColor(static_cast<nzUInt8>(r*255.f), static_cast<nzUInt8>(g*255.f), static_cast<nzUInt8>(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(static_cast<nzUInt8>(r*255.f), static_cast<nzUInt8>(g*255.f), static_cast<nzUInt8>(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')
{
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 == "tr")
{
float alpha;
if (std::sscanf(&m_currentLine[(keyword[0] == 'd') ? 2 : 3], "%f", &alpha) == 1)
{
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->alpha = 1.f - alpha; // tr vaut pour la "valeur de transparence", 0 = opaque
}
#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")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != NzString::npos)
{
NzString map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->ambientMap = map;
}
}
else if (keyword == "map_kd")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != NzString::npos)
{
NzString map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->diffuseMap = map;
}
}
else if (keyword == "map_ks")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != NzString::npos)
{
NzString map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->specularMap = map;
}
}
else if (keyword == "map_bump" || keyword == "bump")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != NzString::npos)
{
NzString map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->bumpMap = map;
}
}
else if (keyword == "map_d")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != NzString::npos)
{
NzString map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->alphaMap = map;
}
}
else if (keyword == "map_decal" || keyword == "decal")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != NzString::npos)
{
NzString map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->decalMap = map;
}
}
else if (keyword == "map_disp" || keyword == "disp")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != NzString::npos)
{
NzString map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->displacementMap = map;
}
}
else if (keyword == "map_refl" || keyword == "refl")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != NzString::npos)
{
NzString map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->reflectionMap = map;
}
}
else if (keyword == "newmtl")
{
NzString materialName = m_currentLine.SubString(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
const MTLParser::Material* MTLParser::GetMaterial(const String& materialName) const
{
auto it = m_materials.find(materialName);
if (it != m_materials.end())
return &it->second;
else
UnrecognizedLine();
#endif
return nullptr;
}
return true;
}
bool NzMTLParser::Advance(bool required)
{
if (!m_keepLastLine)
const std::unordered_map<String, MTLParser::Material>& MTLParser::GetMaterials() const
{
do
{
if (m_stream.EndOfStream())
{
if (required)
Error("Incomplete MTL file");
return false;
}
m_lineCount++;
m_currentLine = m_stream.ReadLine();
m_currentLine = m_currentLine.SubStringTo("#"); // On ignore les commentaires
m_currentLine.Simplify(); // Pour un traitement plus simple
}
while (m_currentLine.IsEmpty());
return m_materials;
}
else
bool MTLParser::Parse()
{
m_keepLastLine = false;
m_lineCount = 0;
m_materials.clear();
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);
Material* currentMaterial = nullptr;
while (Advance(false))
{
String 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 = Color(static_cast<UInt8>(r*255.f), static_cast<UInt8>(g*255.f), static_cast<UInt8>(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 = Color(static_cast<UInt8>(r*255.f), static_cast<UInt8>(g*255.f), static_cast<UInt8>(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 = Color(static_cast<UInt8>(r*255.f), static_cast<UInt8>(g*255.f), static_cast<UInt8>(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')
{
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 == "tr")
{
float alpha;
if (std::sscanf(&m_currentLine[(keyword[0] == 'd') ? 2 : 3], "%f", &alpha) == 1)
{
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->alpha = 1.f - alpha; // tr vaut pour la "valeur de transparence", 0 = opaque
}
#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")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != String::npos)
{
String map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->ambientMap = map;
}
}
else if (keyword == "map_kd")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != String::npos)
{
String map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->diffuseMap = map;
}
}
else if (keyword == "map_ks")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != String::npos)
{
String map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->specularMap = map;
}
}
else if (keyword == "map_bump" || keyword == "bump")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != String::npos)
{
String map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->bumpMap = map;
}
}
else if (keyword == "map_d")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != String::npos)
{
String map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->alphaMap = map;
}
}
else if (keyword == "map_decal" || keyword == "decal")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != String::npos)
{
String map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->decalMap = map;
}
}
else if (keyword == "map_disp" || keyword == "disp")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != String::npos)
{
String map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->displacementMap = map;
}
}
else if (keyword == "map_refl" || keyword == "refl")
{
unsigned int mapPos = m_currentLine.GetWordPosition(1);
if (mapPos != String::npos)
{
String map = m_currentLine.SubString(mapPos);
if (!currentMaterial)
currentMaterial = &m_materials["default"];
currentMaterial->reflectionMap = map;
}
}
else if (keyword == "newmtl")
{
String materialName = m_currentLine.SubString(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 MTLParser::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.SubStringTo("#"); // On ignore les commentaires
m_currentLine.Simplify(); // Pour un traitement plus simple
}
while (m_currentLine.IsEmpty());
}
else
m_keepLastLine = false;
return true;
}
void MTLParser::Error(const String& message)
{
NazaraError(message + " at line #" + String::Number(m_lineCount));
}
void MTLParser::Warning(const String& message)
{
NazaraWarning(message + " at line #" + String::Number(m_lineCount));
}
void MTLParser::UnrecognizedLine(bool error)
{
String message = "Unrecognized \"" + m_currentLine + '"';
if (error)
Error(message);
else
Warning(message);
}
}

View File

@@ -11,446 +11,449 @@
#include <unordered_map>
#include <Nazara/Utility/Debug.hpp>
NzOBJParser::NzOBJParser(NzInputStream& stream) :
m_stream(stream),
m_streamFlags(stream.GetStreamOptions())
namespace Nz
{
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.data();
}
unsigned int NzOBJParser::GetMaterialCount() const
{
return m_materials.size();
}
const NzOBJParser::Mesh* NzOBJParser::GetMeshes() const
{
return m_meshes.data();
}
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.data();
}
unsigned int NzOBJParser::GetNormalCount() const
{
return m_normals.size();
}
const NzVector4f* NzOBJParser::GetPositions() const
{
return m_positions.data();
}
unsigned int NzOBJParser::GetPositionCount() const
{
return m_positions.size();
}
const NzVector3f* NzOBJParser::GetTexCoords() const
{
return m_texCoords.data();
}
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, préparons le terrain
m_normals.reserve(100);
m_positions.reserve(100);
m_texCoords.reserve(100);
// On va regrouper les meshs par nom et par matériau
using FaceVec = std::vector<Face>;
using MatPair = std::pair<FaceVec, unsigned int>;
std::unordered_map<NzString, std::unordered_map<NzString, MatPair>> meshes;
unsigned int matIndex = 0;
auto GetMaterial = [&meshes, &matIndex] (const NzString& mesh, const NzString& material) -> FaceVec*
OBJParser::OBJParser(InputStream& stream) :
m_stream(stream),
m_streamFlags(stream.GetStreamOptions())
{
auto& map = meshes[mesh];
auto it = map.find(material);
if (it == map.end())
it = map.insert(std::make_pair(material, MatPair(FaceVec(), matIndex++))).first;
if ((m_streamFlags & StreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags | StreamOption_Text);
}
return &(it->second.first);
};
// On prépare le mesh par défaut
FaceVec* currentMesh = nullptr;
while (Advance(false))
OBJParser::~OBJParser()
{
switch (std::tolower(m_currentLine[0]))
if ((m_streamFlags & StreamOption_Text) == 0)
m_stream.SetStreamOptions(m_streamFlags);
}
const String* OBJParser::GetMaterials() const
{
return m_materials.data();
}
unsigned int OBJParser::GetMaterialCount() const
{
return m_materials.size();
}
const OBJParser::Mesh* OBJParser::GetMeshes() const
{
return m_meshes.data();
}
unsigned int OBJParser::GetMeshCount() const
{
return m_meshes.size();
}
const String& OBJParser::GetMtlLib() const
{
return m_mtlLib;
}
const Vector3f* OBJParser::GetNormals() const
{
return m_normals.data();
}
unsigned int OBJParser::GetNormalCount() const
{
return m_normals.size();
}
const Vector4f* OBJParser::GetPositions() const
{
return m_positions.data();
}
unsigned int OBJParser::GetPositionCount() const
{
return m_positions.size();
}
const Vector3f* OBJParser::GetTexCoords() const
{
return m_texCoords.data();
}
unsigned int OBJParser::GetTexCoordCount() const
{
return m_texCoords.size();
}
bool OBJParser::Parse()
{
String 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, préparons le terrain
m_normals.reserve(100);
m_positions.reserve(100);
m_texCoords.reserve(100);
// On va regrouper les meshs par nom et par matériau
using FaceVec = std::vector<Face>;
using MatPair = std::pair<FaceVec, unsigned int>;
std::unordered_map<String, std::unordered_map<String, MatPair>> meshes;
unsigned int matIndex = 0;
auto GetMaterial = [&meshes, &matIndex] (const String& mesh, const String& material) -> FaceVec*
{
case 'f': // Une face
auto& map = meshes[mesh];
auto it = map.find(material);
if (it == map.end())
it = map.insert(std::make_pair(material, MatPair(FaceVec(), matIndex++))).first;
return &(it->second.first);
};
// On prépare le mesh par défaut
FaceVec* currentMesh = nullptr;
while (Advance(false))
{
switch (std::tolower(m_currentLine[0]))
{
if (m_currentLine.GetSize() < 7) // Le minimum syndical pour définir une face de trois sommets (f 1 2 3)
case 'f': // Une face
{
#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)
if (m_currentLine.GetSize() < 7) // Le minimum syndical pour définir une face de trois sommets (f 1 2 3)
{
if (std::sscanf(&m_currentLine[pos], "%d//%d%n", &p, &n, &offset) != 2)
#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)
{
if (std::sscanf(&m_currentLine[pos], "%d/%d%n", &p, &t, &offset) != 2)
if (std::sscanf(&m_currentLine[pos], "%d//%d%n", &p, &n, &offset) != 2)
{
if (std::sscanf(&m_currentLine[pos], "%d%n", &p, &offset) != 1)
if (std::sscanf(&m_currentLine[pos], "%d/%d%n", &p, &t, &offset) != 2)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
error = true;
break;
if (std::sscanf(&m_currentLine[pos], "%d%n", &p, &offset) != 1)
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
error = true;
break;
}
else
{
n = 0;
t = 0;
}
}
else
{
n = 0;
t = 0;
}
}
else
n = 0;
t = 0;
}
else
t = 0;
}
if (p < 0)
{
p += m_positions.size();
if (p < 0)
{
Error("Vertex index out of range (" + NzString::Number(p) + " < 0");
error = true;
break;
p += m_positions.size();
if (p < 0)
{
Error("Vertex index out of range (" + String::Number(p) + " < 0");
error = true;
break;
}
}
}
else
p--;
else
p--;
if (n < 0)
{
n += m_normals.size();
if (n < 0)
{
Error("Vertex index out of range (" + NzString::Number(n) + " < 0");
error = true;
break;
n += m_normals.size();
if (n < 0)
{
Error("Vertex index out of range (" + String::Number(n) + " < 0");
error = true;
break;
}
}
}
else
n--;
else
n--;
if (t < 0)
{
t += m_texCoords.size();
if (t < 0)
{
Error("Vertex index out of range (" + NzString::Number(t) + " < 0");
t += m_texCoords.size();
if (t < 0)
{
Error("Vertex index out of range (" + String::Number(t) + " < 0");
error = true;
break;
}
}
else
t--;
if (static_cast<unsigned int>(p) >= m_positions.size())
{
Error("Vertex index out of range (" + String::Number(p) + " >= " + String::Number(m_positions.size()) + ')');
error = true;
break;
}
else if (n >= 0 && static_cast<unsigned int>(n) >= m_normals.size())
{
Error("Normal index out of range (" + String::Number(n) + " >= " + String::Number(m_normals.size()) + ')');
error = true;
break;
}
else if (t >= 0 && static_cast<unsigned int>(t) >= m_texCoords.size())
{
Error("TexCoord index out of range (" + String::Number(t) + " >= " + String::Number(m_texCoords.size()) + ')');
error = true;
break;
}
pos += offset;
}
if (!error)
{
if (!currentMesh)
currentMesh = GetMaterial(meshName, matName);
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.SubString(m_currentLine.GetWordPosition(1));
break;
case 'g':
case 'o':
{
if (m_currentLine.GetSize() <= 2 || m_currentLine[1] != ' ')
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
String objectName = m_currentLine.SubString(m_currentLine.GetWordPosition(1));
if (objectName.IsEmpty())
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
meshName = objectName;
currentMesh = GetMaterial(meshName, matName);
break;
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
case 's':
if (m_currentLine.GetSize() <= 2 || m_currentLine[1] == ' ')
{
String param = m_currentLine.SubString(2);
if (param != "all" && param != "on" && param != "off" && !param.IsNumber())
UnrecognizedLine();
}
else
t--;
if (static_cast<unsigned int>(p) >= m_positions.size())
{
Error("Vertex index out of range (" + NzString::Number(p) + " >= " + NzString::Number(m_positions.size()) + ')');
error = true;
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()) + ')');
error = true;
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()) + ')');
error = true;
break;
}
pos += offset;
}
if (!error)
{
if (!currentMesh)
currentMesh = GetMaterial(meshName, matName);
currentMesh->push_back(std::move(face));
}
break;
}
case 'm':
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (m_currentLine.GetWord(0).ToLower() != "mtllib")
UnrecognizedLine();
UnrecognizedLine();
break;
#endif
m_mtlLib = m_currentLine.SubString(m_currentLine.GetWordPosition(1));
break;
case 'u':
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (m_currentLine.GetWord(0) != "usemtl")
UnrecognizedLine();
#endif
case 'g':
case 'o':
{
if (m_currentLine.GetSize() <= 2 || m_currentLine[1] != ' ')
matName = m_currentLine.SubString(m_currentLine.GetWordPosition(1));
if (matName.IsEmpty())
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
currentMesh = GetMaterial(meshName, matName);
break;
case 'v':
{
String word = m_currentLine.GetWord(0).ToLower();
if (word == 'v')
{
Vector4f vertex(Vector3f::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")
{
Vector3f normal(Vector3f::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")
{
Vector3f uvw(Vector3f::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;
}
NzString objectName = m_currentLine.SubString(m_currentLine.GetWordPosition(1));
if (objectName.IsEmpty())
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
meshName = objectName;
currentMesh = GetMaterial(meshName, matName);
break;
}
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
case 's':
if (m_currentLine.GetSize() <= 2 || m_currentLine[1] == ' ')
{
NzString param = m_currentLine.SubString(2);
if (param != "all" && param != "on" && param != "off" && !param.IsNumber())
UnrecognizedLine();
}
else
UnrecognizedLine();
break;
#endif
case 'u':
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
if (m_currentLine.GetWord(0) != "usemtl")
UnrecognizedLine();
#endif
matName = m_currentLine.SubString(m_currentLine.GetWordPosition(1));
if (matName.IsEmpty())
{
#if NAZARA_UTILITY_STRICT_RESOURCE_PARSING
UnrecognizedLine();
#endif
break;
}
currentMesh = GetMaterial(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::unordered_map<NzString, unsigned int> materials;
m_materials.resize(matIndex);
std::unordered_map<String, unsigned int> materials;
m_materials.resize(matIndex);
for (auto& meshIt : meshes)
{
for (auto& matIt : meshIt.second)
for (auto& meshIt : meshes)
{
auto& faceVec = matIt.second.first;
unsigned int index = matIt.second.second;
if (!faceVec.empty())
for (auto& matIt : meshIt.second)
{
Mesh mesh;
mesh.faces = std::move(faceVec);
mesh.name = meshIt.first;
auto it = materials.find(matIt.first);
if (it == materials.end())
auto& faceVec = matIt.second.first;
unsigned int index = matIt.second.second;
if (!faceVec.empty())
{
mesh.material = index;
materials[matIt.first] = index;
m_materials[index] = matIt.first;
Mesh mesh;
mesh.faces = std::move(faceVec);
mesh.name = meshIt.first;
auto it = materials.find(matIt.first);
if (it == materials.end())
{
mesh.material = index;
materials[matIt.first] = index;
m_materials[index] = matIt.first;
}
else
mesh.material = it->second;
m_meshes.emplace_back(std::move(mesh));
}
else
mesh.material = it->second;
m_meshes.emplace_back(std::move(mesh));
}
}
}
if (m_meshes.empty())
{
NazaraError("No meshes");
return false;
}
return true;
}
bool NzOBJParser::Advance(bool required)
{
if (!m_keepLastLine)
{
do
if (m_meshes.empty())
{
if (m_stream.EndOfStream())
{
if (required)
Error("Incomplete OBJ file");
return false;
}
m_lineCount++;
m_currentLine = m_stream.ReadLine();
m_currentLine = m_currentLine.SubStringTo("#"); // On ignore les commentaires
m_currentLine.Simplify(); // Pour un traitement plus simple
NazaraError("No meshes");
return false;
}
while (m_currentLine.IsEmpty());
return true;
}
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);
bool OBJParser::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.SubStringTo("#"); // On ignore les commentaires
m_currentLine.Simplify(); // Pour un traitement plus simple
}
while (m_currentLine.IsEmpty());
}
else
m_keepLastLine = false;
return true;
}
void OBJParser::Error(const String& message)
{
NazaraError(message + " at line #" + String::Number(m_lineCount));
}
void OBJParser::Warning(const String& message)
{
NazaraWarning(message + " at line #" + String::Number(m_lineCount));
}
void OBJParser::UnrecognizedLine(bool error)
{
String message = "Unrecognized \"" + m_currentLine + '"';
if (error)
Error(message);
else
Warning(message);
}
}

View File

@@ -12,154 +12,101 @@
// Auteur du loader original : David Henry
namespace
namespace Nz
{
struct pcx_header
namespace
{
nzUInt8 manufacturer;
nzUInt8 version;
nzUInt8 encoding;
nzUInt8 bitsPerPixel;
nzUInt16 xmin, ymin;
nzUInt16 xmax, ymax;
nzUInt16 horzRes, vertRes;
nzUInt8 palette[48];
nzUInt8 reserved;
nzUInt8 numColorPlanes;
nzUInt16 bytesPerScanLine;
nzUInt16 paletteType;
nzUInt16 horzSize, vertSize;
nzUInt8 padding[54];
};
static_assert(sizeof(pcx_header) == (6+48+54)*sizeof(nzUInt8) + 10*sizeof(nzUInt16), "pcx_header struct must be packed");
bool IsSupported(const NzString& extension)
{
return (extension == "pcx");
}
nzTernary Check(NzInputStream& stream, const NzImageParams& parameters)
{
NazaraUnused(parameters);
nzUInt8 manufacturer;
if (stream.Read(&manufacturer, 1) == 1)
struct pcx_header
{
if (manufacturer == 0x0a)
return nzTernary_True;
UInt8 manufacturer;
UInt8 version;
UInt8 encoding;
UInt8 bitsPerPixel;
UInt16 xmin, ymin;
UInt16 xmax, ymax;
UInt16 horzRes, vertRes;
UInt8 palette[48];
UInt8 reserved;
UInt8 numColorPlanes;
UInt16 bytesPerScanLine;
UInt16 paletteType;
UInt16 horzSize, vertSize;
UInt8 padding[54];
};
static_assert(sizeof(pcx_header) == (6+48+54)*sizeof(UInt8) + 10*sizeof(UInt16), "pcx_header struct must be packed");
bool IsSupported(const String& extension)
{
return (extension == "pcx");
}
return nzTernary_False;
}
bool Load(NzImage* image, NzInputStream& stream, const NzImageParams& parameters)
{
NazaraUnused(parameters);
pcx_header header;
if (stream.Read(&header, sizeof(pcx_header)) != sizeof(pcx_header))
Ternary Check(InputStream& stream, const ImageParams& parameters)
{
NazaraError("Failed to read header");
return false;
}
NazaraUnused(parameters);
#ifdef NAZARA_BIG_ENDIAN
// Les fichiers PCX sont en little endian
NzByteSwap(&header.xmin, sizeof(nzUInt16));
NzByteSwap(&header.ymin, sizeof(nzUInt16));
NzByteSwap(&header.xmax, sizeof(nzUInt16));
NzByteSwap(&header.ymax, sizeof(nzUInt16));
NzByteSwap(&header.horzRes, sizeof(nzUInt16));
NzByteSwap(&header.vertRes, sizeof(nzUInt16));
NzByteSwap(&header.bytesPerScanLine, sizeof(nzUInt16));
NzByteSwap(&header.paletteType, sizeof(nzUInt16));
NzByteSwap(&header.horzSize, sizeof(nzUInt16));
NzByteSwap(&header.vertSize, sizeof(nzUInt16));
#endif
unsigned int bitCount = header.bitsPerPixel * header.numColorPlanes;
unsigned int width = header.xmax - header.xmin+1;
unsigned int height = header.ymax - header.ymin+1;
if (!image->Create(nzImageType_2D, nzPixelFormat_RGB8, width, height, 1, (parameters.levelCount > 0) ? parameters.levelCount : 1))
{
NazaraError("Failed to create image");
return false;
}
nzUInt8* pixels = image->GetPixels();
int rle_value = 0;
unsigned int rle_count = 0;
switch (bitCount)
{
case 1:
UInt8 manufacturer;
if (stream.Read(&manufacturer, 1) == 1)
{
for (unsigned int y = 0; y < height; ++y)
{
nzUInt8* ptr = &pixels[y * width * 3];
int bytes = header.bytesPerScanLine;
/* decode line number y */
while (bytes--)
{
if (rle_count == 0)
{
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
return false;
}
if (rle_value < 0xc0)
rle_count = 1;
else
{
rle_count = rle_value - 0xc0;
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
return false;
}
}
}
rle_count--;
for (int i = 7; i >= 0; --i)
{
int colorIndex = ((rle_value & (1 << i)) > 0);
*ptr++ = header.palette[colorIndex * 3 + 0];
*ptr++ = header.palette[colorIndex * 3 + 1];
*ptr++ = header.palette[colorIndex * 3 + 2];
}
}
}
break;
if (manufacturer == 0x0a)
return Ternary_True;
}
case 4:
return Ternary_False;
}
bool Load(Image* image, InputStream& stream, const ImageParams& parameters)
{
NazaraUnused(parameters);
pcx_header header;
if (stream.Read(&header, sizeof(pcx_header)) != sizeof(pcx_header))
{
std::unique_ptr<nzUInt8[]> colorIndex(new nzUInt8[width]);
std::unique_ptr<nzUInt8[]> line(new nzUInt8[header.bytesPerScanLine]);
NazaraError("Failed to read header");
return false;
}
for (unsigned int y = 0; y < height; ++y)
#ifdef NAZARA_BIG_ENDIAN
// Les fichiers PCX sont en little endian
SwapBytes(&header.xmin, sizeof(UInt16));
SwapBytes(&header.ymin, sizeof(UInt16));
SwapBytes(&header.xmax, sizeof(UInt16));
SwapBytes(&header.ymax, sizeof(UInt16));
SwapBytes(&header.horzRes, sizeof(UInt16));
SwapBytes(&header.vertRes, sizeof(UInt16));
SwapBytes(&header.bytesPerScanLine, sizeof(UInt16));
SwapBytes(&header.paletteType, sizeof(UInt16));
SwapBytes(&header.horzSize, sizeof(UInt16));
SwapBytes(&header.vertSize, sizeof(UInt16));
#endif
unsigned int bitCount = header.bitsPerPixel * header.numColorPlanes;
unsigned int width = header.xmax - header.xmin+1;
unsigned int height = header.ymax - header.ymin+1;
if (!image->Create(ImageType_2D, PixelFormatType_RGB8, width, height, 1, (parameters.levelCount > 0) ? parameters.levelCount : 1))
{
NazaraError("Failed to create image");
return false;
}
UInt8* pixels = image->GetPixels();
int rle_value = 0;
unsigned int rle_count = 0;
switch (bitCount)
{
case 1:
{
nzUInt8* ptr = &pixels[y * width * 3];
std::memset(colorIndex.get(), 0, width);
for (unsigned int c = 0; c < 4; ++c)
for (unsigned int y = 0; y < height; ++y)
{
nzUInt8* pLine = line.get();
UInt8* ptr = &pixels[y * width * 3];
int bytes = header.bytesPerScanLine;
/* decode line number y */
@@ -169,7 +116,7 @@ namespace
{
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
@@ -180,114 +127,125 @@ namespace
rle_count = rle_value - 0xc0;
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
}
}
rle_count--;
*(pLine++) = rle_value;
for (int i = 7; i >= 0; --i)
{
int colorIndex = ((rle_value & (1 << i)) > 0);
*ptr++ = header.palette[colorIndex * 3 + 0];
*ptr++ = header.palette[colorIndex * 3 + 1];
*ptr++ = header.palette[colorIndex * 3 + 2];
}
}
}
break;
}
case 4:
{
std::unique_ptr<UInt8[]> colorIndex(new UInt8[width]);
std::unique_ptr<UInt8[]> line(new UInt8[header.bytesPerScanLine]);
for (unsigned int y = 0; y < height; ++y)
{
UInt8* ptr = &pixels[y * width * 3];
std::memset(colorIndex.get(), 0, width);
for (unsigned int c = 0; c < 4; ++c)
{
UInt8* pLine = line.get();
int bytes = header.bytesPerScanLine;
/* decode line number y */
while (bytes--)
{
if (rle_count == 0)
{
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
if (rle_value < 0xc0)
rle_count = 1;
else
{
rle_count = rle_value - 0xc0;
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
}
}
rle_count--;
*(pLine++) = rle_value;
}
/* compute line's color indexes */
for (unsigned int x = 0; x < width; ++x)
{
if (line[x / 8] & (128 >> (x % 8)))
colorIndex[x] += (1 << c);
}
}
/* compute line's color indexes */
/* decode scan line. color index => rgb */
for (unsigned int x = 0; x < width; ++x)
{
if (line[x / 8] & (128 >> (x % 8)))
colorIndex[x] += (1 << c);
*ptr++ = header.palette[colorIndex[x] * 3 + 0];
*ptr++ = header.palette[colorIndex[x] * 3 + 1];
*ptr++ = header.palette[colorIndex[x] * 3 + 2];
}
}
/* decode scan line. color index => rgb */
for (unsigned int x = 0; x < width; ++x)
break;
}
case 8:
{
UInt8 palette[768];
/* the palette is contained in the last 769 bytes of the file */
UInt64 curPos = stream.GetCursorPos();
stream.SetCursorPos(stream.GetSize()-769);
UInt8 magic;
if (!stream.Read(&magic, 1))
{
*ptr++ = header.palette[colorIndex[x] * 3 + 0];
*ptr++ = header.palette[colorIndex[x] * 3 + 1];
*ptr++ = header.palette[colorIndex[x] * 3 + 2];
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
}
break;
}
case 8:
{
nzUInt8 palette[768];
/* the palette is contained in the last 769 bytes of the file */
nzUInt64 curPos = stream.GetCursorPos();
stream.SetCursorPos(stream.GetSize()-769);
nzUInt8 magic;
if (!stream.Read(&magic, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
return false;
}
/* first byte must be equal to 0x0c (12) */
if (magic != 0x0c)
{
NazaraError("Colormap's first byte must be 0x0c (0x" + NzString::Number(magic, 16) + ')');
return false;
}
/* read palette */
if (stream.Read(palette, 768) != 768)
{
NazaraError("Failed to read palette");
return false;
}
stream.SetCursorPos(curPos);
/* read pixel data */
for (unsigned int y = 0; y < height; ++y)
{
nzUInt8* ptr = &pixels[y * width * 3];
int bytes = header.bytesPerScanLine;
/* decode line number y */
while (bytes--)
/* first byte must be equal to 0x0c (12) */
if (magic != 0x0c)
{
if (rle_count == 0)
{
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
return false;
}
if (rle_value < 0xc0)
rle_count = 1;
else
{
rle_count = rle_value - 0xc0;
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
return false;
}
}
}
rle_count--;
*ptr++ = palette[rle_value * 3 + 0];
*ptr++ = palette[rle_value * 3 + 1];
*ptr++ = palette[rle_value * 3 + 2];
NazaraError("Colormap's first byte must be 0x0c (0x" + String::Number(magic, 16) + ')');
return false;
}
}
break;
}
case 24:
{
for (unsigned int y = 0; y < height; ++y)
{
/* for each color plane */
for (int c = 0; c < 3; ++c)
/* read palette */
if (stream.Read(palette, 768) != 768)
{
nzUInt8* ptr = &pixels[y * width * 3];
NazaraError("Failed to read palette");
return false;
}
stream.SetCursorPos(curPos);
/* read pixel data */
for (unsigned int y = 0; y < height; ++y)
{
UInt8* ptr = &pixels[y * width * 3];
int bytes = header.bytesPerScanLine;
/* decode line number y */
@@ -297,7 +255,7 @@ namespace
{
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
@@ -308,39 +266,87 @@ namespace
rle_count = rle_value - 0xc0;
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + NzString::Number(stream.GetCursorPos()) + ')');
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
}
}
rle_count--;
ptr[c] = static_cast<nzUInt8>(rle_value);
ptr += 3;
*ptr++ = palette[rle_value * 3 + 0];
*ptr++ = palette[rle_value * 3 + 1];
*ptr++ = palette[rle_value * 3 + 2];
}
}
break;
}
break;
case 24:
{
for (unsigned int y = 0; y < height; ++y)
{
/* for each color plane */
for (int c = 0; c < 3; ++c)
{
UInt8* ptr = &pixels[y * width * 3];
int bytes = header.bytesPerScanLine;
/* decode line number y */
while (bytes--)
{
if (rle_count == 0)
{
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
if (rle_value < 0xc0)
rle_count = 1;
else
{
rle_count = rle_value - 0xc0;
if (!stream.Read(&rle_value, 1))
{
NazaraError("Failed to read stream (byte " + String::Number(stream.GetCursorPos()) + ')');
return false;
}
}
}
rle_count--;
ptr[c] = static_cast<UInt8>(rle_value);
ptr += 3;
}
}
}
break;
}
default:
NazaraError("Unsupported " + String::Number(bitCount) + " bitcount for pcx files");
return false;
}
default:
NazaraError("Unsupported " + NzString::Number(bitCount) + " bitcount for pcx files");
return false;
if (parameters.loadFormat != PixelFormatType_Undefined)
image->Convert(parameters.loadFormat);
return true;
}
}
namespace Loaders
{
void RegisterPCX()
{
ImageLoader::RegisterLoader(IsSupported, Check, Load);
}
if (parameters.loadFormat != nzPixelFormat_Undefined)
image->Convert(parameters.loadFormat);
return true;
void UnregisterPCX()
{
ImageLoader::UnregisterLoader(IsSupported, Check, Load);
}
}
}
void NzLoaders_PCX_Register()
{
NzImageLoader::RegisterLoader(IsSupported, Check, Load);
}
void NzLoaders_PCX_Unregister()
{
NzImageLoader::UnregisterLoader(IsSupported, Check, Load);
}

View File

@@ -9,7 +9,13 @@
#include <Nazara/Prerequesites.hpp>
void NzLoaders_PCX_Register();
void NzLoaders_PCX_Unregister();
namespace Nz
{
namespace Loaders
{
void RegisterPCX();
void UnregisterPCX();
}
}
#endif // NAZARA_LOADERS_PCX_HPP

View File

@@ -13,82 +13,88 @@
#include <set>
#include <Nazara/Utility/Debug.hpp>
namespace
namespace Nz
{
int Read(void* userdata, char* data, int size)
namespace
{
NzInputStream* stream = static_cast<NzInputStream*>(userdata);
return static_cast<int>(stream->Read(data, size));
}
void Skip(void* userdata, int size)
{
NzInputStream* stream = static_cast<NzInputStream*>(userdata);
stream->SetCursorPos(static_cast<nzInt64>(stream->GetCursorPos()) + static_cast<nzInt64>(size));
}
int Eof(void* userdata)
{
NzInputStream* stream = static_cast<NzInputStream*>(userdata);
return stream->GetCursorPos() >= stream->GetSize();
}
static stbi_io_callbacks callbacks = {Read, Skip, Eof};
bool IsSupported(const NzString& extension)
{
static std::set<NzString> supportedExtensions = {"bmp", "gif", "hdr", "jpg", "jpeg", "pic", "png", "ppm", "pgm", "psd", "tga"};
return supportedExtensions.find(extension) != supportedExtensions.end();
}
nzTernary Check(NzInputStream& stream, const NzImageParams& parameters)
{
NazaraUnused(parameters);
int width, height, bpp;
if (stbi_info_from_callbacks(&callbacks, &stream, &width, &height, &bpp))
return nzTernary_True;
else
return nzTernary_False;
}
bool Load(NzImage* image, NzInputStream& stream, const NzImageParams& parameters)
{
// Je charge tout en RGBA8 et je converti ensuite via la méthode Convert
// Ceci à cause d'un bug de STB lorsqu'il s'agit de charger certaines images (ex: JPG) en "default"
int width, height, bpp;
nzUInt8* ptr = stbi_load_from_callbacks(&callbacks, &stream, &width, &height, &bpp, STBI_rgb_alpha);
if (!ptr)
int Read(void* userdata, char* data, int size)
{
NazaraError("Failed to load image: " + NzString(stbi_failure_reason()));
return false;
InputStream* stream = static_cast<InputStream*>(userdata);
return static_cast<int>(stream->Read(data, size));
}
if (!image->Create(nzImageType_2D, nzPixelFormat_RGBA8, width, height, 1, (parameters.levelCount > 0) ? parameters.levelCount : 1))
void Skip(void* userdata, int size)
{
NazaraError("Failed to create image");
InputStream* stream = static_cast<InputStream*>(userdata);
stream->SetCursorPos(static_cast<Int64>(stream->GetCursorPos()) + static_cast<Int64>(size));
}
int Eof(void* userdata)
{
InputStream* stream = static_cast<InputStream*>(userdata);
return stream->GetCursorPos() >= stream->GetSize();
}
static stbi_io_callbacks callbacks = {Read, Skip, Eof};
bool IsSupported(const String& extension)
{
static std::set<String> supportedExtensions = {"bmp", "gif", "hdr", "jpg", "jpeg", "pic", "png", "ppm", "pgm", "psd", "tga"};
return supportedExtensions.find(extension) != supportedExtensions.end();
}
Ternary Check(InputStream& stream, const ImageParams& parameters)
{
NazaraUnused(parameters);
int width, height, bpp;
if (stbi_info_from_callbacks(&callbacks, &stream, &width, &height, &bpp))
return Ternary_True;
else
return Ternary_False;
}
bool Load(Image* image, InputStream& stream, const ImageParams& parameters)
{
// Je charge tout en RGBA8 et je converti ensuite via la méthode Convert
// Ceci à cause d'un bug de STB lorsqu'il s'agit de charger certaines images (ex: JPG) en "default"
int width, height, bpp;
UInt8* ptr = stbi_load_from_callbacks(&callbacks, &stream, &width, &height, &bpp, STBI_rgb_alpha);
if (!ptr)
{
NazaraError("Failed to load image: " + String(stbi_failure_reason()));
return false;
}
if (!image->Create(ImageType_2D, PixelFormatType_RGBA8, width, height, 1, (parameters.levelCount > 0) ? parameters.levelCount : 1))
{
NazaraError("Failed to create image");
stbi_image_free(ptr);
return false;
}
image->Update(ptr);
stbi_image_free(ptr);
return false;
if (parameters.loadFormat != PixelFormatType_Undefined)
image->Convert(parameters.loadFormat);
return true;
}
}
namespace Loaders
{
void RegisterSTB()
{
ImageLoader::RegisterLoader(IsSupported, Check, Load);
}
image->Update(ptr);
stbi_image_free(ptr);
if (parameters.loadFormat != nzPixelFormat_Undefined)
image->Convert(parameters.loadFormat);
return true;
void UnregisterSTB()
{
ImageLoader::UnregisterLoader(IsSupported, Check, Load);
}
}
}
void NzLoaders_STB_Register()
{
NzImageLoader::RegisterLoader(IsSupported, Check, Load);
}
void NzLoaders_STB_Unregister()
{
NzImageLoader::UnregisterLoader(IsSupported, Check, Load);
}

View File

@@ -9,7 +9,13 @@
#include <Nazara/Prerequesites.hpp>
void NzLoaders_STB_Register();
void NzLoaders_STB_Unregister();
namespace Nz
{
namespace Loaders
{
void RegisterSTB();
void UnregisterSTB();
}
}
#endif // NAZARA_LOADERS_STB_HPP