188 lines
5.8 KiB

#include "athena/SpriteFileReader.hpp"
#include "athena/SpriteFile.hpp"
#include "athena/Sprite.hpp"
#include "athena/SpritePart.hpp"
#include "athena/SpriteFrame.hpp"
#include "athena/Utility.hpp"
namespace athena::io {
SpriteFileReader::SpriteFileReader(atUint8* data, atUint64 length) : MemoryCopyReader(data, length) {}
SpriteFileReader::SpriteFileReader(const std::string& filepath) : MemoryCopyReader(filepath) {}
Sakura::SpriteFile* SpriteFileReader::readFile() {
Sakura::SpriteFile* ret = NULL;
atUint32 magic = readUint32();
if (magic != Sakura::SpriteFile::Magic) {
atError("Not a valid Sakura Sprite container");
return nullptr;
atUint32 version = readUint32();
// TODO: Make this more verbose
if (version != Sakura::SpriteFile::Version) {
atError("Unsupported version");
return nullptr;
// After reading in the magic and version we need to load some
// metadata about the file.
// Such as the texture count, it's dimensions, and it's origin.
// After that we have the number of sprites contained in this
// sprite container.
atUint16 textureCount =
readUint16(); // Having it as a Uint16 gives us the ability to have up to 65536 different states
// This is probably overkill, but it's better safe than sorry.
atUint32 width = readUint32();
atUint32 height = readUint32();
float originX = readFloat();
float originY = readFloat();
atUint16 spriteCount = readUint16();
// Lets go ahead and create or new container.
ret = new Sakura::SpriteFile(width, height, originX, originY);
// The next four bytes are reserved to keep the header 32 byte aligned.
// This isn't necessary for most systems, but it's eventually planned
// to migrate this code to Big Endian based systems, such as the wii
// which require data to be 32 byte aligned, or it causes some issues.
// It's also convenient to have this, for later expansion.
atUint32 reserved = readUint32();
// Next we have to load the textures
// If we tried to add them one at a time to the sprite container
// it will be slow as hell, so we store them in a vector locally
// then give that vector the the container, this bypasses the de-reference
// for each texture
std::vector<Sakura::STexture*> textures;
QList<Sakura::STexture*> textures;
for (atUint16 i = 0; i < textureCount; i++) {
Sakura::STexture* texture = new Sakura::STexture;
texture->Filepath = readString();
texture->Preload = readBool();
// Now for the sprites
// The sprites are a bit more difficult, they are stored in an unordered_map
// with it's name as the key, this means we can't have two sprites with the same name
// Normally this isn't a problem, but someone may decide to copy and paste a sprite
// and forget to change the name, that needs to be handled, but it's outside the scope
// of this reader.
std::unordered_map<std::string, Sakura::Sprite*> sprites;
QMap<QString, Sakura::Sprite*> sprites;
for (atUint16 i = 0; i < spriteCount; i++) {
Sakura::Sprite* sprite = new Sakura::Sprite(ret);
std::string name = readString();
QString name = QString::fromStdString(readString());
atUint16 frameCount = readUint16();
atUint16 stateCount = readUint16();
// Each state id corresponds to a texture held in the parent class
std::vector<int> stateIds;
for (int j = 0; j < stateCount; j++)
// Now to read the sprite parts.
// The parts allow us to build retro style sprites very easily
// making it possible to use one texture atlas for all possible
// frame combinations, this reduces the amount of memory overhead
// and the storage footprint, while Sakura supports packs and zips
// it's still a bad idea to have a metric ton of texture resources
// littering the place
std::vector<Sakura::SpriteFrame*> frames;
QList<Sakura::SpriteFrame*> frames;
for (atUint32 k = 0; k < frameCount; k++) {
Sakura::SpriteFrame* frame = new Sakura::SpriteFrame(sprite);
atUint16 partCount = readUint16();
std::vector<Sakura::SpritePart*> parts;
QList<Sakura::SpritePart*> parts;
for (atUint8 j = 0; j < partCount; j++) {
Sakura::SpritePart* part = new Sakura::SpritePart(frame);
std::string name = readString();
QString name = QString::fromStdString(readString());
float xOff = readFloat();
float yOff = readFloat();
part->setOffset(xOff, yOff);
float texXOff = readFloat();
float texYOff = readFloat();
part->setTextureOffset(texXOff, texYOff);
atUint32 width = readUint32();
atUint32 height = readUint32();
part->setSize(width, height);
bool flippedH = readBool();
bool flippedV = readBool();
if (sprite->name() != std::string()) {
std::string nameLow(sprite->name());
sprites[nameLow] = sprite;
if (!sprite->name().isEmpty())
sprites[sprite->name().toLower()] = sprite;
else {
atError("Sprite names cannot be empty");
return nullptr;
return ret;
} // namespace athena::io