Switch from Nz prefix to namespace Nz
What a huge commit Former-commit-id: 38ac5eebf70adc1180f571f6006192d28fb99897
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
NzByteSwap(&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
|
||||
NzByteSwap(&header.size, sizeof(UInt32));
|
||||
NzByteSwap(&header.flags, sizeof(UInt32));
|
||||
NzByteSwap(&header.height, sizeof(UInt32));
|
||||
NzByteSwap(&header.width, sizeof(UInt32));
|
||||
NzByteSwap(&header.pitch, sizeof(UInt32));
|
||||
NzByteSwap(&header.depth, sizeof(UInt32));
|
||||
NzByteSwap(&header.levelCount, sizeof(UInt32));
|
||||
|
||||
// DDS_PixelFormat
|
||||
NzByteSwap(&header.format.size, sizeof(UInt32));
|
||||
NzByteSwap(&header.format.flags, sizeof(UInt32));
|
||||
NzByteSwap(&header.format.fourCC, sizeof(UInt32));
|
||||
NzByteSwap(&header.format.bpp, sizeof(UInt32));
|
||||
NzByteSwap(&header.format.redMask, sizeof(UInt32));
|
||||
NzByteSwap(&header.format.greenMask, sizeof(UInt32));
|
||||
NzByteSwap(&header.format.blueMask, sizeof(UInt32));
|
||||
NzByteSwap(&header.format.alphaMask, sizeof(UInt32));
|
||||
|
||||
NzByteSwap(&header.ddsCaps1, sizeof(UInt32));
|
||||
NzByteSwap(&header.ddsCaps2, sizeof(UInt32));
|
||||
NzByteSwap(&header.ddsCaps3, sizeof(UInt32));
|
||||
NzByteSwap(&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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user