From b43754986fc459ced57c5d527c3cd9e62d643fdb Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Wed, 24 Aug 2016 18:31:26 +1000 Subject: [PATCH] Load sprite sheet into new atlas structure --- src/AssetManager.c | 2 +- src/Common.c | 61 ++++++++ src/WorldTraveller.c | 219 ++++++++++++++++++++++------- src/include/Dengine/AssetManager.h | 2 + src/include/Dengine/Assets.h | 19 +++ src/include/Dengine/Common.h | 17 +++ 6 files changed, 265 insertions(+), 55 deletions(-) diff --git a/src/AssetManager.c b/src/AssetManager.c index 52cbc17..8a2307c 100644 --- a/src/AssetManager.c +++ b/src/AssetManager.c @@ -117,7 +117,7 @@ const i32 asset_loadTextureImage(AssetManager *assetManager, if (imgWidth != imgHeight) { printf( - "worldTraveller_gameInit() warning: Sprite sheet is not square: " + "asset_loadTextureImage() warning: Sprite sheet is not square: " "%dx%dpx\n", imgWidth, imgHeight); } #endif diff --git a/src/Common.c b/src/Common.c index 0cadc0b..f0e274f 100644 --- a/src/Common.c +++ b/src/Common.c @@ -21,6 +21,16 @@ i32 common_strcmp(const char *a, const char *b) 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) { for (i32 i = 0; i < numChars; i++) @@ -112,3 +122,54 @@ i32 common_atoi(const char *string, const i32 len) 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; +} diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index f7ed6d6..392bbf2 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -289,9 +289,17 @@ INTERNAL void assetInit(GameState *state) XmlNode root = {0}; XmlNode *node = &root; 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++) { XmlToken *token = &xmlTokens[i]; + switch (token->type) { @@ -305,7 +313,16 @@ INTERNAL void assetInit(GameState *state) if (common_strcmp(nextToken->string, node->parent->name) == 0) { 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 { @@ -360,6 +377,7 @@ INTERNAL void assetInit(GameState *state) case xmltokentype_closeArrow: { XmlToken prevToken = xmlTokens[i - 1]; + prevNode = node; /* Closed node means we can return to parent */ 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 DEBUG_RECURSIVE_PRINT_XML_TREE(&root); #endif @@ -411,73 +432,164 @@ INTERNAL void assetInit(GameState *state) { if(common_strcmp(node->name, "TextureAtlas") == 0) { - XmlNode *atlasXmlNode = node; - XmlNode *atlasChildNode = atlasXmlNode->child; - while (atlasChildNode) + XmlNode *atlasXmlNode = node; + TexAtlasEntry *atlasEntry = NULL; + 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; - while (subTextureAttrib) +#ifdef DENGINE_DEBUG + + // 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 - Rect rect = {0}; + XmlAttribute *subTextureAttrib = + &atlasChildNode->attribute; - // 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; + AtlasSubTexture newSubTexEntry = {0}; + while (subTextureAttrib) + { - 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) - { - char *name = subTextureAttrib->name; - i32 nameLen = common_strlen(name); - rect.pos.x = CAST(f32) common_atoi(name, nameLen); - } - else if (common_strcmp(subTextureAttrib->name, "y") == - 0) - { - char *name = subTextureAttrib->name; - i32 nameLen = common_strlen(name); - rect.pos.y = CAST(f32) common_atoi(name, nameLen); - } - else if (common_strcmp(subTextureAttrib->name, - "width") == 0) - { - 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 + ASSERT(newSubTexEntry.name) +#endif + + + u32 subTexHashIndex = common_murmurHash2( + newSubTexEntry.name, common_strlen(newSubTexEntry.name), + RANDOM_SEED); + subTexHashIndex = + subTexHashIndex % ARRAY_COUNT(atlasEntry->subTex); + + // NOTE(doyle): Hash collision + AtlasSubTexture *subTexEntry = + &atlasEntry->subTex[subTexHashIndex]; + if (subTexEntry->name) { #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 + 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 - DEBUG_LOG("Unsupported xml node name not parsed"); + DEBUG_LOG("Unsupported xml node name not parsed"); #endif - } + } - atlasChildNode = atlasChildNode->next; + atlasChildNode = atlasChildNode->next; + } + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG("Unsupported xml node"); +#endif } } else @@ -486,7 +598,6 @@ INTERNAL void assetInit(GameState *state) DEBUG_LOG("Unsupported xml node name not parsed"); #endif } - node = node->next; } diff --git a/src/include/Dengine/AssetManager.h b/src/include/Dengine/AssetManager.h index a2131de..691068c 100644 --- a/src/include/Dengine/AssetManager.h +++ b/src/include/Dengine/AssetManager.h @@ -16,6 +16,8 @@ typedef struct AssetManager Animation anims[32]; AudioVorbis audio[32]; Font font; + + TexAtlasEntry texAtlas_[8]; } AssetManager; #define MAX_TEXTURE_SIZE 1024 diff --git a/src/include/Dengine/Assets.h b/src/include/Dengine/Assets.h index b78f11f..e8a1a35 100644 --- a/src/include/Dengine/Assets.h +++ b/src/include/Dengine/Assets.h @@ -81,6 +81,25 @@ typedef struct AudioVorbis i32 size; } 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 { // TODO(doyle): String hash based lookup diff --git a/src/include/Dengine/Common.h b/src/include/Dengine/Common.h index ce5a661..3ce4263 100644 --- a/src/include/Dengine/Common.h +++ b/src/include/Dengine/Common.h @@ -30,6 +30,7 @@ typedef double f64; i32 common_strlen(const char *const string); 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_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); 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