Load sprite sheet into new atlas structure

This commit is contained in:
Doyle Thai 2016-08-24 18:31:26 +10:00
parent 84a2c5e382
commit b43754986f
6 changed files with 265 additions and 55 deletions

View File

@ -117,7 +117,7 @@ const i32 asset_loadTextureImage(AssetManager *assetManager,
if (imgWidth != imgHeight) if (imgWidth != imgHeight)
{ {
printf( printf(
"worldTraveller_gameInit() warning: Sprite sheet is not square: " "asset_loadTextureImage() warning: Sprite sheet is not square: "
"%dx%dpx\n", imgWidth, imgHeight); "%dx%dpx\n", imgWidth, imgHeight);
} }
#endif #endif

View File

@ -21,6 +21,16 @@ i32 common_strcmp(const char *a, const char *b)
return ((*a < *b) ? -1 : 1); return ((*a < *b) ? -1 : 1);
} }
void common_strncat(char *dest, const char *src, i32 numChars)
{
char *stringPtr = dest;
while (*stringPtr)
stringPtr++;
for (i32 i = 0; i < numChars; i++)
*(stringPtr++) = src[i];
}
char *common_strncpy(char *dest, const char *src, i32 numChars) char *common_strncpy(char *dest, const char *src, i32 numChars)
{ {
for (i32 i = 0; i < numChars; i++) for (i32 i = 0; i < numChars; i++)
@ -112,3 +122,54 @@ i32 common_atoi(const char *string, const i32 len)
return result; return result;
} }
u32 common_murmurHash2(const void *key, i32 len, u32 seed)
{
// 'm' and 'r' are mixing constants generated offline.
// They're not really 'magic', they just happen to work well.
const u32 m = 0x5bd1e995;
const i32 r = 24;
// Initialize the hash to a 'random' value
u32 h = seed ^ len;
// Mix 4 bytes at a time into the hash
const unsigned char * data = (const unsigned char *)key;
while(len >= 4)
{
u32 k = *(u32 *)data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
// Handle the last few bytes of the input array
switch(len)
{
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0];
h *= m;
};
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}

View File

@ -289,9 +289,17 @@ INTERNAL void assetInit(GameState *state)
XmlNode root = {0}; XmlNode root = {0};
XmlNode *node = &root; XmlNode *node = &root;
node->parent = node; node->parent = node;
// NOTE(doyle): Used for when closing a node with many children. We
// automatically assign the next child after each child close within
// a group. Hence on the last child, we open another node but the next
// token indicates the group is closing- we need to set the last child's
// next reference to NULL
XmlNode *prevNode = NULL;
for (i32 i = 0; i < tokenIndex; i++) for (i32 i = 0; i < tokenIndex; i++)
{ {
XmlToken *token = &xmlTokens[i]; XmlToken *token = &xmlTokens[i];
switch (token->type) switch (token->type)
{ {
@ -305,7 +313,16 @@ INTERNAL void assetInit(GameState *state)
if (common_strcmp(nextToken->string, node->parent->name) == 0) if (common_strcmp(nextToken->string, node->parent->name) == 0)
{ {
node->parent->isClosed = TRUE; node->parent->isClosed = TRUE;
node = node->parent;
if (prevNode)
{
prevNode->next = NULL;
}
XmlNode *parent = node->parent;
PLATFORM_MEM_FREE(arena, node, sizeof(XmlNode));
node = node->parent;
} }
else else
{ {
@ -360,6 +377,7 @@ INTERNAL void assetInit(GameState *state)
case xmltokentype_closeArrow: case xmltokentype_closeArrow:
{ {
XmlToken prevToken = xmlTokens[i - 1]; XmlToken prevToken = xmlTokens[i - 1];
prevNode = node;
/* Closed node means we can return to parent */ /* Closed node means we can return to parent */
if (prevToken.type == xmltokentype_backslash) if (prevToken.type == xmltokentype_backslash)
@ -402,6 +420,9 @@ INTERNAL void assetInit(GameState *state)
} }
} }
// TODO(doyle): Use a proper random seed
#define RANDOM_SEED 0xDEADBEEF
#if 1 #if 1
DEBUG_RECURSIVE_PRINT_XML_TREE(&root); DEBUG_RECURSIVE_PRINT_XML_TREE(&root);
#endif #endif
@ -411,73 +432,164 @@ INTERNAL void assetInit(GameState *state)
{ {
if(common_strcmp(node->name, "TextureAtlas") == 0) if(common_strcmp(node->name, "TextureAtlas") == 0)
{ {
XmlNode *atlasXmlNode = node; XmlNode *atlasXmlNode = node;
XmlNode *atlasChildNode = atlasXmlNode->child; TexAtlasEntry *atlasEntry = NULL;
while (atlasChildNode) if (common_strcmp(node->attribute.name, "imagePath") == 0)
{ {
if (common_strcmp(atlasChildNode->name, "SubTexture") == 0) char *imageName = atlasXmlNode->attribute.value;
u32 atlasHashIndex = common_murmurHash2(
imageName, common_strlen(imageName), RANDOM_SEED);
atlasHashIndex =
atlasHashIndex % ARRAY_COUNT(assetManager->texAtlas_);
atlasEntry = &assetManager->texAtlas_[atlasHashIndex];
if (atlasEntry->name)
{ {
XmlAttribute *subTextureAttrib = &atlasChildNode->attribute; #ifdef DENGINE_DEBUG
while (subTextureAttrib)
// NOTE(doyle): Two atlas textures have the same access name
ASSERT(common_strcmp(atlasEntry->name, imageName) != 0);
#endif
while (atlasEntry->next)
atlasEntry = atlasEntry->next;
atlasEntry->next =
PLATFORM_MEM_ALLOC(arena, 1, TexAtlasEntry);
atlasEntry = atlasEntry->next;
}
char *dataDir = "data/textures/WorldTraveller/";
char imagePath[512] = {0};
common_strncat(imagePath, dataDir, common_strlen(dataDir));
common_strncat(imagePath, imageName, common_strlen(imageName));
asset_loadTextureImage(assetManager, imagePath, texlist_claude);
atlasEntry->name =
PLATFORM_MEM_ALLOC(arena, common_strlen(imageName), char);
common_strncpy(atlasEntry->name, imageName,
common_strlen(imageName));
atlasEntry->tex =
asset_getTexture(assetManager, texlist_claude);
XmlNode *atlasChildNode = atlasXmlNode->child;
while (atlasChildNode)
{
if (common_strcmp(atlasChildNode->name, "SubTexture") == 0)
{ {
// TODO(doyle): Fill in details properly XmlAttribute *subTextureAttrib =
Rect rect = {0}; &atlasChildNode->attribute;
// TODO(doyle): Work around for now in xml reading, AtlasSubTexture newSubTexEntry = {0};
// reading the last node closing node not being merged while (subTextureAttrib)
// to the parent {
if (!subTextureAttrib->name) continue;
if (common_strcmp(subTextureAttrib->name, "name") == 0) // TODO(doyle): Work around for now in xml reading,
{ // reading the last node closing node not being
// merged to the parent
if (!subTextureAttrib->name) continue;
if (common_strcmp(subTextureAttrib->name, "name") ==
0)
{
char *value = subTextureAttrib->value;
newSubTexEntry.name = value;
}
else if (common_strcmp(subTextureAttrib->name,
"x") == 0)
{
char *value = subTextureAttrib->value;
i32 valueLen = common_strlen(value);
newSubTexEntry.rect.pos.x =
CAST(f32) common_atoi(value, valueLen);
}
else if (common_strcmp(subTextureAttrib->name,
"y") == 0)
{
char *value = subTextureAttrib->value;
i32 valueLen = common_strlen(value);
newSubTexEntry.rect.pos.y =
CAST(f32) common_atoi(value, valueLen);
}
else if (common_strcmp(subTextureAttrib->name,
"width") == 0)
{
char *value = subTextureAttrib->value;
i32 valueLen = common_strlen(value);
newSubTexEntry.rect.size.w =
CAST(f32) common_atoi(value, valueLen);
}
else if (common_strcmp(subTextureAttrib->name,
"height") == 0)
{
char *value = subTextureAttrib->value;
i32 valueLen = common_strlen(value);
newSubTexEntry.rect.size.h =
CAST(f32) common_atoi(value, valueLen);
}
else
{
#ifdef DENGINE_DEBUG
DEBUG_LOG(
"Unsupported xml attribute in SubTexture");
#endif
}
subTextureAttrib = subTextureAttrib->next;
} }
else if (common_strcmp(subTextureAttrib->name, "x") ==
0) #ifdef DENGINE_DEBUG
{ ASSERT(newSubTexEntry.name)
char *name = subTextureAttrib->name; #endif
i32 nameLen = common_strlen(name);
rect.pos.x = CAST(f32) common_atoi(name, nameLen);
} u32 subTexHashIndex = common_murmurHash2(
else if (common_strcmp(subTextureAttrib->name, "y") == newSubTexEntry.name, common_strlen(newSubTexEntry.name),
0) RANDOM_SEED);
{ subTexHashIndex =
char *name = subTextureAttrib->name; subTexHashIndex % ARRAY_COUNT(atlasEntry->subTex);
i32 nameLen = common_strlen(name);
rect.pos.y = CAST(f32) common_atoi(name, nameLen); // NOTE(doyle): Hash collision
} AtlasSubTexture *subTexEntry =
else if (common_strcmp(subTextureAttrib->name, &atlasEntry->subTex[subTexHashIndex];
"width") == 0) if (subTexEntry->name)
{
char *name = subTextureAttrib->name;
i32 nameLen = common_strlen(name);
rect.size.w = CAST(f32) common_atoi(name, nameLen);
}
else if (common_strcmp(subTextureAttrib->name,
"height") == 0)
{
char *name = subTextureAttrib->name;
i32 nameLen = common_strlen(name);
rect.size.h = CAST(f32) common_atoi(name, nameLen);
}
else
{ {
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
DEBUG_LOG(
"Unsupported xml attribute in SubTexture"); // NOTE(doyle): Two textures have the same access
// name
ASSERT(common_strcmp(subTexEntry->name,
newSubTexEntry.name) != 0);
#endif #endif
while (subTexEntry->next)
subTexEntry = subTexEntry->next;
subTexEntry->next =
PLATFORM_MEM_ALLOC(arena, 1, AtlasSubTexture);
subTexEntry = subTexEntry->next;
} }
subTextureAttrib = subTextureAttrib->next; *subTexEntry = newSubTexEntry;
common_strncpy(subTexEntry->name, newSubTexEntry.name,
common_strlen(newSubTexEntry.name));
} }
} else
else {
{
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
DEBUG_LOG("Unsupported xml node name not parsed"); DEBUG_LOG("Unsupported xml node name not parsed");
#endif #endif
} }
atlasChildNode = atlasChildNode->next; atlasChildNode = atlasChildNode->next;
}
}
else
{
#ifdef DENGINE_DEBUG
DEBUG_LOG("Unsupported xml node");
#endif
} }
} }
else else
@ -486,7 +598,6 @@ INTERNAL void assetInit(GameState *state)
DEBUG_LOG("Unsupported xml node name not parsed"); DEBUG_LOG("Unsupported xml node name not parsed");
#endif #endif
} }
node = node->next; node = node->next;
} }

View File

@ -16,6 +16,8 @@ typedef struct AssetManager
Animation anims[32]; Animation anims[32];
AudioVorbis audio[32]; AudioVorbis audio[32];
Font font; Font font;
TexAtlasEntry texAtlas_[8];
} AssetManager; } AssetManager;
#define MAX_TEXTURE_SIZE 1024 #define MAX_TEXTURE_SIZE 1024

View File

@ -81,6 +81,25 @@ typedef struct AudioVorbis
i32 size; i32 size;
} AudioVorbis; } AudioVorbis;
typedef struct AtlasSubTexture
{
char *name;
Rect rect;
// NOTE(doyle): For hashing collisions
struct AtlasSubTexture *next;
} AtlasSubTexture;
typedef struct TexAtlasEntry
{
char *name;
Texture *tex;
AtlasSubTexture subTex[1024];
// NOTE(doyle): For hashing collisions
struct TexAtlasEntry *next;
} TexAtlasEntry;
typedef struct TexAtlas typedef struct TexAtlas
{ {
// TODO(doyle): String hash based lookup // TODO(doyle): String hash based lookup

View File

@ -30,6 +30,7 @@ typedef double f64;
i32 common_strlen(const char *const string); i32 common_strlen(const char *const string);
i32 common_strcmp(const char *a, const char *b); i32 common_strcmp(const char *a, const char *b);
void common_strncat(char *dest, const char *src, i32 numChars);
char *common_strncpy(char *dest, const char *src, i32 numChars); char *common_strncpy(char *dest, const char *src, i32 numChars);
char *common_memset(char *const ptr, const i32 value, const i32 numBytes); char *common_memset(char *const ptr, const i32 value, const i32 numBytes);
@ -37,4 +38,20 @@ char *common_memset(char *const ptr, const i32 value, const i32 numBytes);
void common_itoa(i32 value, char *buf, i32 bufSize); void common_itoa(i32 value, char *buf, i32 bufSize);
i32 common_atoi(const char *string, const i32 len); i32 common_atoi(const char *string, const i32 len);
//-----------------------------------------------------------------------------
// MurmurHash2, by Austin Appleby
// Note - This code makes a few assumptions about how your machine behaves -
// 1. We can read a 4-byte value from any address without crashing
// 2. sizeof(int) == 4
// And it has a few limitations -
// 1. It will not work incrementally.
// 2. It will not produce the same results on little-endian and big-endian
// machines.
u32 common_murmurHash2(const void *key, i32 len, u32 seed);
#endif #endif