Start merging hash table access into assets

Basic implementation with many pre-existing features disabled untill all
parts of the system that interact with the assets transitions over.
This commit is contained in:
Doyle Thai 2016-08-25 02:06:32 +10:00
parent b43754986f
commit 7ca42f781a
9 changed files with 349 additions and 220 deletions

View File

@ -21,6 +21,76 @@
#include "Dengine/OpenGL.h" #include "Dengine/OpenGL.h"
#include "Dengine/Platform.h" #include "Dengine/Platform.h"
INTERNAL AtlasSubTexture *getAtlasSubTexture(TexAtlas *atlas, char *key)
{
u32 hashIndex = common_getHashIndex(key, ARRAY_COUNT(atlas->subTex));
AtlasSubTexture *result = &atlas->subTex[hashIndex];
if (result->key)
{
while (result && common_strcmp(result->key, key) != 0)
result = result->next;
}
return result;
}
INTERNAL AtlasSubTexture *makeAtlasSubTexture(TexAtlas *atlas,
MemoryArena *arena, char *key)
{
u32 hashIndex = common_getHashIndex(key, ARRAY_COUNT(atlas->subTex));
AtlasSubTexture *result = &atlas->subTex[hashIndex];
if (result->key)
{
while (result->next)
{
if (common_strcmp(result->key, key) == 0)
{
// TODO(doyle): Error correction whereby if a tex atlas already
// exists
ASSERT(INVALID_CODE_PATH);
}
result = result->next;
}
result->next = PLATFORM_MEM_ALLOC(arena, 1, AtlasSubTexture);
result = result->next;
}
return result;
}
INTERNAL Animation *getFreeAnimSlot(Animation *table, u32 tableSize,
MemoryArena *arena, char *key)
{
u32 hashIndex = common_getHashIndex(key, tableSize);
Animation *result = &table[hashIndex];
if (result->key)
{
while (result->next)
{
if (common_strcmp(result->key, key) == 0)
{
// TODO(doyle): Error correction whereby if a tex atlas already
// exists
ASSERT(INVALID_CODE_PATH);
}
result = result->next;
}
result->next = PLATFORM_MEM_ALLOC(arena, 1, Animation);
result = result->next;
}
return result;
}
Rect asset_getAtlasSubTexRect(TexAtlas *atlas, char *key)
{
AtlasSubTexture *subTex = getAtlasSubTexture(atlas, key);
Rect result = subTex->rect;
return result;
}
// TODO(doyle): Switch to hash based lookup // TODO(doyle): Switch to hash based lookup
// TODO(doyle): Use pointers, so we can forward declare all assets? // TODO(doyle): Use pointers, so we can forward declare all assets?
AudioVorbis *asset_getVorbis(AssetManager *assetManager, AudioVorbis *asset_getVorbis(AssetManager *assetManager,
@ -49,27 +119,64 @@ Texture *asset_getTexture(AssetManager *const assetManager,
return NULL; return NULL;
} }
TexAtlas *asset_getTextureAtlas(AssetManager *assetManager, const enum TexList type) TexAtlas *asset_makeTexAtlas(AssetManager *const assetManager,
MemoryArena *arena, const char *const key)
{ {
if (type < texlist_count) u32 hashIndex = common_getHashIndex(key, ARRAY_COUNT(assetManager->texAtlas));
return &assetManager->texAtlas[type]; TexAtlas *result = &assetManager->texAtlas[hashIndex];
if (result->key)
{
while (result->next)
{
if (common_strcmp(result->key, key) == 0)
{
// TODO(doyle): Error correction whereby if a tex atlas already
// exists
ASSERT(INVALID_CODE_PATH);
}
result = result->next;
}
#ifdef DENGINE_DEBUG result->next = PLATFORM_MEM_ALLOC(arena, 1, TexAtlas);
ASSERT(INVALID_CODE_PATH); result = result->next;
#endif }
return NULL;
return result;
} }
Animation *asset_getAnim(AssetManager *assetManager, const enum AnimList type) TexAtlas *asset_getTexAtlas(AssetManager *const assetManager,
const char *const key)
{ {
if (type < animlist_count) u32 hashIndex = common_getHashIndex(key, ARRAY_COUNT(assetManager->texAtlas));
return &assetManager->anims[type]; TexAtlas *result = &assetManager->texAtlas[hashIndex];
if (result->key)
{
while (result && common_strcmp(result->key, key) != 0)
result = result->next;
}
else
{
return NULL;
}
#ifdef DENGINE_DEBUG return result;
ASSERT(INVALID_CODE_PATH); }
#endif
return NULL; Animation *asset_getAnim(AssetManager *assetManager, char *key)
{
u32 hashIndex = common_getHashIndex(key, ARRAY_COUNT(assetManager->anims));
Animation *result = &assetManager->anims[hashIndex];
if (result->key)
{
while (result && common_strcmp(result->key, key) != 0)
result = result->next;
}
else
{
return NULL;
}
return result;
} }
const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena, const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena,
@ -364,7 +471,8 @@ const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena,
// TODO(doyle): We copy over the bitmap direct to the font sheet, should we // TODO(doyle): We copy over the bitmap direct to the font sheet, should we
// align the baselines up so we don't need to do baseline adjusting at // align the baselines up so we don't need to do baseline adjusting at
// render? // render?
i32 atlasIndex = 0; char charToEncode = CAST(char)codepointRange.x;
TexAtlas *fontAtlas = asset_makeTexAtlas(assetManager, arena, "font");
for (i32 row = 0; row < MAX_TEXTURE_SIZE; row++) for (i32 row = 0; row < MAX_TEXTURE_SIZE; row++)
{ {
u32 *destRow = fontBitmap + (row * MAX_TEXTURE_SIZE); u32 *destRow = fontBitmap + (row * MAX_TEXTURE_SIZE);
@ -378,23 +486,26 @@ const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena,
/* Store the location of glyph into atlas */ /* Store the location of glyph into atlas */
if (verticalPixelsBlitted == 0) if (verticalPixelsBlitted == 0)
{ {
TexAtlas *fontAtlas = &assetManager->texAtlas[texlist_font];
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
ASSERT(activeGlyph.codepoint < ARRAY_COUNT(fontAtlas->texRect)); ASSERT(activeGlyph.codepoint < ARRAY_COUNT(fontAtlas->subTex));
#endif #endif
v2 origin = v2 origin =
V2(CAST(f32)(glyphIndex * font->maxSize.w), CAST(f32) row); V2(CAST(f32)(glyphIndex * font->maxSize.w), CAST(f32) row);
#if 1
fontAtlas->texRect[atlasIndex++] = // NOTE(doyle): Since charToEncode starts from 0 and we record
math_getRect(origin, V2(CAST(f32) font->maxSize.w, // all ascii characters, charToEncode represents the character
CAST(f32) font->maxSize.h)); // 1:1
#else char charTmp[2] = {0};
v2i fontSize = charTmp[0] = charToEncode;
font->charMetrics[activeGlyph.codepoint - 32].trueSize; AtlasSubTexture *subTex =
fontAtlas->texRect[atlasIndex++] = math_getRect( makeAtlasSubTexture(fontAtlas, arena, charTmp);
origin, V2(CAST(f32) fontSize.x, CAST(f32) fontSize.y));
#endif subTex->key = PLATFORM_MEM_ALLOC(arena, 1, char);
subTex->key[0] = charToEncode;
subTex->rect = CAST(Rect){origin, font->maxSize};
charToEncode++;
} }
/* Copy over exactly one row of pixels */ /* Copy over exactly one row of pixels */
@ -452,8 +563,8 @@ const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena,
#endif #endif
PLATFORM_MEM_FREE(arena, fontBitmap, bitmapSize); PLATFORM_MEM_FREE(arena, fontBitmap, bitmapSize);
font->tex = &assetManager->textures[texlist_font]; fontAtlas->tex = &assetManager->textures[texlist_font];
font->atlas = &assetManager->texAtlas[texlist_font]; font->atlas = fontAtlas;
// NOTE(doyle): Formula derived from STB Font // NOTE(doyle): Formula derived from STB Font
font->verticalSpacing = font->verticalSpacing =
@ -472,26 +583,28 @@ const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena,
return 0; return 0;
} }
void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena, void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena,
i32 texId, i32 animId, i32 *frameIndex, i32 numFrames, char *animName, TexAtlas *atlas, char **subTextureNames,
f32 frameDuration) i32 numSubTextures, f32 frameDuration)
{ {
#ifdef DENGINE_DEBUG Animation *anim = getFreeAnimSlot(
ASSERT(assetManager && frameIndex) assetManager->anims, ARRAY_COUNT(assetManager->anims), arena, animName);
ASSERT(!assetManager->anims[animId].frameIndex);
#endif
Animation anim = {0}; anim->atlas = atlas;
anim.atlas = asset_getTextureAtlas(assetManager, texId); anim->frameDuration = frameDuration;
anim->numFrames = numSubTextures;
anim.frameIndex = PLATFORM_MEM_ALLOC(arena, numFrames, i32); // NOTE(doyle): +1 for the null terminator
for (i32 i = 0; i < numFrames; i++) anim.frameIndex[i] = frameIndex[i]; anim->name = PLATFORM_MEM_ALLOC(arena, common_strlen(animName) + 1, char);
common_strncpy(anim->name, animName, common_strlen(animName));
anim.numFrames = numFrames; anim->frameList = PLATFORM_MEM_ALLOC(arena, numSubTextures, char*);
anim.frameDuration = frameDuration; for (i32 i = 0; i < numSubTextures; i++)
{
AtlasSubTexture *subTex = getAtlasSubTexture(atlas, subTextureNames[i]);
anim->frameList[i] = subTex->key;
}
assetManager->anims[animId] = anim;
} }
v2 asset_stringDimInPixels(const Font *const font, const char *const string) v2 asset_stringDimInPixels(const Font *const font, const char *const string)

View File

@ -3,20 +3,28 @@
#include "Dengine/Platform.h" #include "Dengine/Platform.h"
#include "Dengine/WorldTraveller.h" #include "Dengine/WorldTraveller.h"
void entity_setActiveAnim(Entity *entity, enum AnimList animId) void entity_setActiveAnim(Entity *entity, char *animName)
{ {
#ifdef DENGINE_DEBUG
ASSERT(animId < animlist_count);
ASSERT(entity->anim[animId].anim);
#endif
/* Reset current anim data */ /* Reset current anim data */
EntityAnim_ *currAnim = &entity->anim[entity->currAnimId]; EntityAnim *currEntityAnim = &entity->animList[entity->currAnimId];
currAnim->currDuration = currAnim->anim->frameDuration; currEntityAnim->currDuration = currEntityAnim->anim->frameDuration;
currAnim->currFrame = 0; currEntityAnim->currFrame = 0;
/* Set entity active animation */ /* Set entity active animation */
entity->currAnimId = animId; for (i32 i = 0; i < ARRAY_COUNT(entity->animList); i++)
{
Animation *anim = entity->animList[i].anim;
if (anim)
{
if (common_strcmp(anim->key, animName) == 0)
{
entity->currAnimId = i;
return;
}
}
}
DEBUG_LOG("Entity does not have access to desired anim");
} }
void entity_updateAnim(Entity *entity, f32 dt) void entity_updateAnim(Entity *entity, f32 dt)
@ -24,20 +32,15 @@ void entity_updateAnim(Entity *entity, f32 dt)
if (!entity->tex) if (!entity->tex)
return; return;
// TODO(doyle): Recheck why we have this twice EntityAnim *currEntityAnim = &entity->animList[entity->currAnimId];
EntityAnim_ *entityAnim = &entity->anim[entity->currAnimId]; Animation *anim = currEntityAnim->anim;
Animation anim = *entityAnim->anim;
i32 frameIndex = anim.frameIndex[entityAnim->currFrame];
v4 texRect = anim.atlas->texRect[frameIndex];
entityAnim->currDuration -= dt; currEntityAnim->currDuration -= dt;
if (entityAnim->currDuration <= 0.0f) if (currEntityAnim->currDuration <= 0.0f)
{ {
entityAnim->currFrame++; currEntityAnim->currFrame++;
entityAnim->currFrame = entityAnim->currFrame % anim.numFrames; currEntityAnim->currFrame = currEntityAnim->currFrame % anim->numFrames;
frameIndex = entityAnim->anim->frameIndex[entityAnim->currFrame]; currEntityAnim->currDuration = anim->frameDuration;
texRect = anim.atlas->texRect[frameIndex];
entityAnim->currDuration = anim.frameDuration;
} }
// NOTE(doyle): If humanoid entity, let animation dictate render size which // NOTE(doyle): If humanoid entity, let animation dictate render size which
@ -47,18 +50,31 @@ void entity_updateAnim(Entity *entity, f32 dt)
case entitytype_hero: case entitytype_hero:
case entitytype_mob: case entitytype_mob:
case entitytype_npc: case entitytype_npc:
entity->renderSize = math_getRectSize(texRect); char *frameName = anim->frameList[currEntityAnim->currFrame];
Rect texRect =
asset_getAtlasSubTexRect(anim->atlas, frameName);
entity->renderSize = texRect.size;
default: default:
break; break;
} }
} }
void entity_addAnim(AssetManager *assetManager, Entity *entity, i32 animId) void entity_addAnim(AssetManager *assetManager, Entity *entity, char *animName)
{ {
Animation *anim = asset_getAnim(assetManager, animId); i32 freeAnimIndex = 0;
entity->anim[animId].anim = anim; for (i32 i = 0; i < ARRAY_COUNT(entity->animList); i++)
entity->anim[animId].currFrame = 0; {
entity->anim[animId].currDuration = anim->frameDuration; EntityAnim *entityAnim = &entity->animList[i];
if (!entityAnim->anim)
{
entityAnim->anim = asset_getAnim(assetManager, animName);
entityAnim->currFrame = 0;
entityAnim->currDuration = entityAnim->anim->frameDuration;
return;
}
}
DEBUG_LOG("No more free entity animation slots");
} }
void entity_addGenericMob(MemoryArena *arena, AssetManager *assetManager, void entity_addGenericMob(MemoryArena *arena, AssetManager *assetManager,
@ -81,12 +97,7 @@ void entity_addGenericMob(MemoryArena *arena, AssetManager *assetManager,
mob->audioRenderer->sourceIndex = AUDIO_SOURCE_UNASSIGNED; mob->audioRenderer->sourceIndex = AUDIO_SOURCE_UNASSIGNED;
/* Populate mob animation references */ /* Populate mob animation references */
entity_addAnim(assetManager, mob, animlist_hero_idle); entity_addAnim(assetManager, mob, "Claude idle");
entity_addAnim(assetManager, mob, animlist_hero_walk);
entity_addAnim(assetManager, mob, animlist_hero_wave);
entity_addAnim(assetManager, mob, animlist_hero_battlePose);
entity_addAnim(assetManager, mob, animlist_hero_tackle);
mob->currAnimId = animlist_hero_idle;
} }
Entity *entity_add(MemoryArena *arena, World *world, v2 pos, v2 size, Entity *entity_add(MemoryArena *arena, World *world, v2 pos, v2 size,

View File

@ -175,16 +175,13 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera,
v2 pivotPoint, f32 rotate, v4 color) v2 pivotPoint, f32 rotate, v4 color)
{ {
i32 strLen = common_strlen(string); i32 strLen = common_strlen(string);
// TODO(doyle): Scale, not too important .. but rudimentary infrastructure
// laid out here
f32 scale = 1.0f;
// TODO(doyle): Slightly incorrect string length in pixels calculation, // TODO(doyle): Slightly incorrect string length in pixels calculation,
// because we use the advance metric of each character for length not // because we use the advance metric of each character for length not
// maximum character size in rendering // maximum character size in rendering
v2 rightAlignedP = v2 rightAlignedP =
v2_add(pos, V2(scale *(CAST(f32) font->maxSize.w * CAST(f32) strLen), v2_add(pos, V2((CAST(f32) font->maxSize.w * CAST(f32) strLen),
scale * CAST(f32) font->maxSize.h)); CAST(f32) font->maxSize.h));
v2 leftAlignedP = pos; v2 leftAlignedP = pos;
if (math_pointInRect(camera, leftAlignedP) || if (math_pointInRect(camera, leftAlignedP) ||
math_pointInRect(camera, rightAlignedP)) math_pointInRect(camera, rightAlignedP))
@ -197,27 +194,32 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera,
pos = posInCameraSpace; pos = posInCameraSpace;
// TODO(doyle): Find why font is 1px off, might be arial font semantics // TODO(doyle): Find why font is 1px off, might be arial font semantics
Texture *tex = font->atlas->tex;
f32 baseline = pos.y - font->verticalSpacing + 1; f32 baseline = pos.y - font->verticalSpacing + 1;
for (i32 i = 0; i < strLen; i++) for (i32 i = 0; i < strLen; i++)
{ {
// NOTE(doyle): Atlas packs fonts tightly, so offset the codepoint
// to its actual atlas index, i.e. we skip the first 31 glyphs
i32 codepoint = string[i]; i32 codepoint = string[i];
i32 relativeIndex = CAST(i32)(codepoint - font->codepointRange.x); i32 relativeIndex = CAST(i32)(codepoint - font->codepointRange.x);
CharMetrics charMetric = font->charMetrics[relativeIndex]; CharMetrics charMetric = font->charMetrics[relativeIndex];
pos.y = baseline - (scale * charMetric.offset.y); pos.y = baseline - (charMetric.offset.y);
const v4 charRectOnScreen = const v4 charRectOnScreen =
math_getRect(pos, V2(scale * CAST(f32) font->maxSize.w, math_getRect(pos, V2(CAST(f32) font->maxSize.w,
scale * CAST(f32) font->maxSize.h)); CAST(f32) font->maxSize.h));
pos.x += scale * charMetric.advance; pos.x += charMetric.advance;
/* Get texture out */ /* Get texture out */
v4 charTexRect = font->atlas->texRect[relativeIndex]; Rect charTexRect =
flipTexCoord(&charTexRect, FALSE, TRUE); asset_getAtlasSubTexRect(font->atlas, &CAST(char)codepoint);
RenderTex renderTex = {font->tex, charTexRect};
v4 deprecatedTexRect = {0};
deprecatedTexRect.vec2[0] = charTexRect.pos;
deprecatedTexRect.vec2[1] = v2_add(charTexRect.pos, charTexRect.size);
flipTexCoord(&deprecatedTexRect, FALSE, TRUE);
RenderTex renderTex = {tex, deprecatedTexRect};
RenderQuad charQuad = RenderQuad charQuad =
createTexQuad(renderer, charRectOnScreen, renderTex); createTexQuad(renderer, charRectOnScreen, renderTex);
stringQuads[quadIndex++] = charQuad; stringQuads[quadIndex++] = charQuad;
@ -228,7 +230,7 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera,
// we're rendering a window sized buffer // we're rendering a window sized buffer
updateBufferObject(renderer, stringQuads, quadIndex); updateBufferObject(renderer, stringQuads, quadIndex);
renderObject(renderer, V2(0.0f, 0.0f), renderer->size, pivotPoint, renderObject(renderer, V2(0.0f, 0.0f), renderer->size, pivotPoint,
rotate, color, font->tex); rotate, color, tex);
PLATFORM_MEM_FREE(arena, stringQuads, strLen * sizeof(RenderQuad)); PLATFORM_MEM_FREE(arena, stringQuads, strLen * sizeof(RenderQuad));
} }
} }
@ -246,12 +248,17 @@ void renderer_entity(Renderer *renderer, Rect camera, Entity *entity,
if (math_pointInRect(camera, leftAlignedP) || if (math_pointInRect(camera, leftAlignedP) ||
math_pointInRect(camera, rightAlignedP)) math_pointInRect(camera, rightAlignedP))
{ {
EntityAnim_ *entityAnim = &entity->anim[entity->currAnimId]; EntityAnim *entityAnim = &entity->animList[entity->currAnimId];
Animation *anim = entityAnim->anim; Animation *anim = entityAnim->anim;
i32 frameIndex = anim->frameIndex[entityAnim->currFrame]; char *frameName = anim->frameList[entityAnim->currFrame];
v4 animTexRect = anim->atlas->texRect[frameIndex]; Rect animRect = asset_getAtlasSubTexRect(anim->atlas, frameName);
if (entity->direction == direction_east) // TODO(doyle): Switch to rect
v4 animTexRect = {0};
animTexRect.vec2[0] = animRect.pos;
animTexRect.vec2[1] = v2_add(animRect.pos, animRect.size);
if (entity->direction == direction_east)
{ {
flipTexCoord(&animTexRect, TRUE, FALSE); flipTexCoord(&animTexRect, TRUE, FALSE);
} }

View File

@ -420,9 +420,6 @@ 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
@ -433,31 +430,11 @@ 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;
TexAtlasEntry *atlasEntry = NULL; TexAtlas *atlasEntry = NULL;
if (common_strcmp(node->attribute.name, "imagePath") == 0) if (common_strcmp(node->attribute.name, "imagePath") == 0)
{ {
char *imageName = atlasXmlNode->attribute.value; char *imageName = atlasXmlNode->attribute.value;
u32 atlasHashIndex = common_murmurHash2( atlasEntry = asset_makeTexAtlas(assetManager, arena, imageName);
imageName, common_strlen(imageName), RANDOM_SEED);
atlasHashIndex =
atlasHashIndex % ARRAY_COUNT(assetManager->texAtlas_);
atlasEntry = &assetManager->texAtlas_[atlasHashIndex];
if (atlasEntry->name)
{
#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 *dataDir = "data/textures/WorldTraveller/";
char imagePath[512] = {0}; char imagePath[512] = {0};
@ -466,9 +443,9 @@ INTERNAL void assetInit(GameState *state)
asset_loadTextureImage(assetManager, imagePath, texlist_claude); asset_loadTextureImage(assetManager, imagePath, texlist_claude);
atlasEntry->name = atlasEntry->key =
PLATFORM_MEM_ALLOC(arena, common_strlen(imageName), char); PLATFORM_MEM_ALLOC(arena, common_strlen(imageName)+1, char);
common_strncpy(atlasEntry->name, imageName, common_strncpy(atlasEntry->key, imageName,
common_strlen(imageName)); common_strlen(imageName));
atlasEntry->tex = atlasEntry->tex =
@ -495,39 +472,43 @@ INTERNAL void assetInit(GameState *state)
0) 0)
{ {
char *value = subTextureAttrib->value; char *value = subTextureAttrib->value;
newSubTexEntry.name = value; newSubTexEntry.key = value;
} }
else if (common_strcmp(subTextureAttrib->name, else if (common_strcmp(subTextureAttrib->name,
"x") == 0) "x") == 0)
{ {
char *value = subTextureAttrib->value; char *value = subTextureAttrib->value;
i32 valueLen = common_strlen(value); i32 valueLen = common_strlen(value);
newSubTexEntry.rect.pos.x = i32 intValue = common_atoi(value, valueLen);
CAST(f32) common_atoi(value, valueLen);
newSubTexEntry.rect.pos.x = CAST(f32) intValue;
} }
else if (common_strcmp(subTextureAttrib->name, else if (common_strcmp(subTextureAttrib->name,
"y") == 0) "y") == 0)
{ {
char *value = subTextureAttrib->value; char *value = subTextureAttrib->value;
i32 valueLen = common_strlen(value); i32 valueLen = common_strlen(value);
newSubTexEntry.rect.pos.y =
CAST(f32) common_atoi(value, valueLen); i32 intValue = common_atoi(value, valueLen);
newSubTexEntry.rect.pos.y = CAST(f32) intValue;
} }
else if (common_strcmp(subTextureAttrib->name, else if (common_strcmp(subTextureAttrib->name,
"width") == 0) "width") == 0)
{ {
char *value = subTextureAttrib->value; char *value = subTextureAttrib->value;
i32 valueLen = common_strlen(value); i32 valueLen = common_strlen(value);
newSubTexEntry.rect.size.w = i32 intValue = common_atoi(value, valueLen);
CAST(f32) common_atoi(value, valueLen);
newSubTexEntry.rect.size.w = CAST(f32) intValue;
} }
else if (common_strcmp(subTextureAttrib->name, else if (common_strcmp(subTextureAttrib->name,
"height") == 0) "height") == 0)
{ {
char *value = subTextureAttrib->value; char *value = subTextureAttrib->value;
i32 valueLen = common_strlen(value); i32 valueLen = common_strlen(value);
newSubTexEntry.rect.size.h = i32 intValue = common_atoi(value, valueLen);
CAST(f32) common_atoi(value, valueLen);
newSubTexEntry.rect.size.h = CAST(f32) intValue;
} }
else else
{ {
@ -536,32 +517,37 @@ INTERNAL void assetInit(GameState *state)
"Unsupported xml attribute in SubTexture"); "Unsupported xml attribute in SubTexture");
#endif #endif
} }
subTextureAttrib = subTextureAttrib->next; subTextureAttrib = subTextureAttrib->next;
} }
// TODO(doyle): XML specifies 0,0 top left, we
// prefer 0,0 bottom right, so offset by size since 0,0
// is top left and size creates a bounding box below it
newSubTexEntry.rect.pos.y =
1024 - newSubTexEntry.rect.pos.y;
newSubTexEntry.rect.pos.y -= newSubTexEntry.rect.size.h;
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
ASSERT(newSubTexEntry.name) ASSERT(newSubTexEntry.key)
#endif #endif
u32 subTexHashIndex = common_murmurHash2( u32 subTexHashIndex = common_murmurHash2(
newSubTexEntry.name, common_strlen(newSubTexEntry.name), newSubTexEntry.key,
RANDOM_SEED); common_strlen(newSubTexEntry.key), 0xDEADBEEF);
subTexHashIndex = subTexHashIndex =
subTexHashIndex % ARRAY_COUNT(atlasEntry->subTex); subTexHashIndex % ARRAY_COUNT(atlasEntry->subTex);
// NOTE(doyle): Hash collision // NOTE(doyle): Hash collision
AtlasSubTexture *subTexEntry = AtlasSubTexture *subTexEntry =
&atlasEntry->subTex[subTexHashIndex]; &atlasEntry->subTex[subTexHashIndex];
if (subTexEntry->name) if (subTexEntry->key)
{ {
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
// NOTE(doyle): Two textures have the same access // NOTE(doyle): Two textures have the same access
// name // name
ASSERT(common_strcmp(subTexEntry->name, ASSERT(common_strcmp(subTexEntry->key,
newSubTexEntry.name) != 0); newSubTexEntry.key) != 0);
#endif #endif
while (subTexEntry->next) while (subTexEntry->next)
subTexEntry = subTexEntry->next; subTexEntry = subTexEntry->next;
@ -572,8 +558,12 @@ INTERNAL void assetInit(GameState *state)
} }
*subTexEntry = newSubTexEntry; *subTexEntry = newSubTexEntry;
common_strncpy(subTexEntry->name, newSubTexEntry.name, i32 keyLen = common_strlen(newSubTexEntry.key);
common_strlen(newSubTexEntry.name));
subTexEntry->key =
PLATFORM_MEM_ALLOC(arena, keyLen+1, char);
common_strncpy(subTexEntry->key, newSubTexEntry.key,
keyLen);
} }
else else
{ {
@ -601,34 +591,6 @@ INTERNAL void assetInit(GameState *state)
node = node->next; node = node->next;
} }
/* Load textures */
asset_loadTextureImage(assetManager,
"data/textures/WorldTraveller/TerraSprite1024.png",
texlist_hero);
TexAtlas *heroAtlas = asset_getTextureAtlas(assetManager, texlist_hero);
heroAtlas->texRect[herorects_idle] = V4(746, 920, 804, 1018);
heroAtlas->texRect[herorects_walkA] = V4(641, 920, 699, 1018);
heroAtlas->texRect[herorects_walkB] = V4(849, 920, 904, 1018);
heroAtlas->texRect[herorects_head] = V4(108, 975, 159, 1024);
heroAtlas->texRect[herorects_waveA] = V4(944, 816, 1010, 918);
heroAtlas->texRect[herorects_waveB] = V4(944, 710, 1010, 812);
heroAtlas->texRect[herorects_battlePose] = V4(8, 814, 71, 910);
heroAtlas->texRect[herorects_castA] = V4(428, 814, 493, 910);
heroAtlas->texRect[herorects_castB] = V4(525, 816, 590, 919);
heroAtlas->texRect[herorects_castC] = V4(640, 816, 698, 916);
asset_loadTextureImage(assetManager,
"data/textures/WorldTraveller/Terrain.png",
texlist_terrain);
TexAtlas *terrainAtlas =
asset_getTextureAtlas(assetManager, texlist_terrain);
f32 atlasTileSize = 128.0f;
const i32 texSize = 1024;
v2 texOrigin = V2(0, 768);
terrainAtlas->texRect[terrainrects_ground] =
V4(texOrigin.x, texOrigin.y, texOrigin.x + atlasTileSize,
texOrigin.y + atlasTileSize);
/* Load shaders */ /* Load shaders */
asset_loadShaderFiles(assetManager, arena, "data/shaders/sprite.vert.glsl", asset_loadShaderFiles(assetManager, arena, "data/shaders/sprite.vert.glsl",
"data/shaders/sprite.frag.glsl", "data/shaders/sprite.frag.glsl",
@ -643,6 +605,7 @@ INTERNAL void assetInit(GameState *state)
DEBUG_LOG("Assets loaded"); DEBUG_LOG("Assets loaded");
#endif #endif
#if 0
/* Load animations */ /* Load animations */
f32 duration = 1.0f; f32 duration = 1.0f;
i32 numRects = 1; i32 numRects = 1;
@ -660,7 +623,20 @@ INTERNAL void assetInit(GameState *state)
i32 idleAnimAtlasIndexes[1] = {herorects_idle}; i32 idleAnimAtlasIndexes[1] = {herorects_idle};
asset_addAnimation(assetManager, arena, texlist_hero, animlist_hero_idle, asset_addAnimation(assetManager, arena, texlist_hero, animlist_hero_idle,
idleAnimAtlasIndexes, numRects, duration); idleAnimAtlasIndexes, numRects, duration);
#else
f32 duration = 1.0f;
i32 numRects = 1;
TexAtlas *claudeAtlas =
asset_getTexAtlas(assetManager, "ClaudeSpriteSheet.png");
duration = 1.0f;
numRects = 1;
char *subTextureNames = {"ClaudeSprite_001"};
asset_addAnimation(assetManager, arena, "Claude_idle", claudeAtlas,
&subTextureNames, 1, 1.0f);
#endif
#if 0
// Walk animation // Walk animation
duration = 0.10f; duration = 0.10f;
numRects = 3; numRects = 3;
@ -690,6 +666,8 @@ INTERNAL void assetInit(GameState *state)
herorects_castC}; herorects_castC};
asset_addAnimation(assetManager, arena, texlist_hero, animlist_hero_tackle, asset_addAnimation(assetManager, arena, texlist_hero, animlist_hero_tackle,
tackleAnimAtlasIndexes, numRects, duration); tackleAnimAtlasIndexes, numRects, duration);
#endif
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
DEBUG_LOG("Animations created"); DEBUG_LOG("Animations created");
#endif #endif
@ -739,6 +717,7 @@ INTERNAL void entityInit(GameState *state, v2 windowSize)
CAST(f32) state->tileSize)); CAST(f32) state->tileSize));
world->uniqueIdAccumulator = 0; world->uniqueIdAccumulator = 0;
#if 0
TexAtlas *const atlas = TexAtlas *const atlas =
asset_getTextureAtlas(assetManager, world->texType); asset_getTextureAtlas(assetManager, world->texType);
@ -765,6 +744,7 @@ INTERNAL void entityInit(GameState *state, v2 windowSize)
tile->currAnimId = animlist_terrain; tile->currAnimId = animlist_terrain;
} }
} }
#endif
} }
World *const world = &state->world[state->currWorldIndex]; World *const world = &state->world[state->currWorldIndex];
@ -790,7 +770,7 @@ INTERNAL void entityInit(GameState *state, v2 windowSize)
pos = V2(size.x, CAST(f32) state->tileSize); pos = V2(size.x, CAST(f32) state->tileSize);
type = entitytype_hero; type = entitytype_hero;
dir = direction_east; dir = direction_east;
tex = asset_getTexture(assetManager, texlist_hero); tex = asset_getTexture(assetManager, texlist_claude);
collides = TRUE; collides = TRUE;
Entity *hero = Entity *hero =
entity_add(arena, world, pos, size, type, dir, tex, collides); entity_add(arena, world, pos, size, type, dir, tex, collides);
@ -801,13 +781,10 @@ INTERNAL void entityInit(GameState *state, v2 windowSize)
world->cameraFollowingId = hero->id; world->cameraFollowingId = hero->id;
/* Populate hero animation references */ /* Populate hero animation references */
entity_addAnim(assetManager, hero, animlist_hero_idle); entity_addAnim(assetManager, hero, "Claude_idle");
entity_addAnim(assetManager, hero, animlist_hero_walk); entity_setActiveAnim(hero, "Claude_idle");
entity_addAnim(assetManager, hero, animlist_hero_wave);
entity_addAnim(assetManager, hero, animlist_hero_battlePose);
entity_addAnim(assetManager, hero, animlist_hero_tackle);
hero->currAnimId = animlist_hero_idle;
#if 0
/* Create a NPC */ /* Create a NPC */
pos = V2(hero->pos.x * 3, CAST(f32) state->tileSize); pos = V2(hero->pos.x * 3, CAST(f32) state->tileSize);
size = hero->hitboxSize; size = hero->hitboxSize;
@ -825,6 +802,7 @@ INTERNAL void entityInit(GameState *state, v2 windowSize)
pos = V2(renderer->size.w - (renderer->size.w / 3.0f), pos = V2(renderer->size.w - (renderer->size.w / 3.0f),
CAST(f32) state->tileSize); CAST(f32) state->tileSize);
entity_addGenericMob(arena, assetManager, world, pos); entity_addGenericMob(arena, assetManager, world, pos);
#endif
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
DEBUG_LOG("World populated"); DEBUG_LOG("World populated");
@ -1152,7 +1130,7 @@ INTERNAL inline void updateWorldBattleEntities(World *world, Entity *entity,
INTERNAL inline void resetEntityState(World *world, Entity *entity) INTERNAL inline void resetEntityState(World *world, Entity *entity)
{ {
updateWorldBattleEntities(world, entity, ENTITY_NOT_IN_BATTLE); updateWorldBattleEntities(world, entity, ENTITY_NOT_IN_BATTLE);
entity_setActiveAnim(entity, animlist_hero_idle); entity_setActiveAnim(entity, "Claude_idle");
entity->stats->busyDuration = 0; entity->stats->busyDuration = 0;
entity->stats->actionTimer = entity->stats->actionRate; entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->queuedAttack = entityattack_invalid; entity->stats->queuedAttack = entityattack_invalid;
@ -1196,7 +1174,7 @@ INTERNAL void entityStateSwitch(EventQueue *eventQueue, World *world,
// or not (i.e. has moved out of frame last frame). // or not (i.e. has moved out of frame last frame).
case entitystate_dead: case entitystate_dead:
registerEvent(eventQueue, eventtype_entity_died, CAST(void *)entity); registerEvent(eventQueue, eventtype_entity_died, CAST(void *)entity);
entity_setActiveAnim(entity, animlist_hero_idle); entity_setActiveAnim(entity, "Claude_idle");
entity->stats->busyDuration = 0; entity->stats->busyDuration = 0;
entity->stats->actionTimer = entity->stats->actionRate; entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->queuedAttack = entityattack_invalid; entity->stats->queuedAttack = entityattack_invalid;
@ -1236,7 +1214,7 @@ INTERNAL void entityStateSwitch(EventQueue *eventQueue, World *world,
switch (newState) switch (newState)
{ {
case entitystate_battle: case entitystate_battle:
entity_setActiveAnim(entity, animlist_hero_battlePose); entity_setActiveAnim(entity, "Claude_idle");
entity->stats->actionTimer = entity->stats->actionRate; entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->busyDuration = 0; entity->stats->busyDuration = 0;
break; break;
@ -1288,6 +1266,7 @@ typedef struct AttackSpec
INTERNAL void beginAttack(EventQueue *eventQueue, World *world, INTERNAL void beginAttack(EventQueue *eventQueue, World *world,
Entity *attacker) Entity *attacker)
{ {
#if 0
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
ASSERT(attacker->stats->entityIdToAttack != ENTITY_NULL_ID); ASSERT(attacker->stats->entityIdToAttack != ENTITY_NULL_ID);
ASSERT(attacker->state == entitystate_battle); ASSERT(attacker->state == entitystate_battle);
@ -1313,6 +1292,7 @@ INTERNAL void beginAttack(EventQueue *eventQueue, World *world,
#endif #endif
break; break;
} }
#endif
} }
// TODO(doyle): MemArena here is temporary until we incorporate AttackSpec to // TODO(doyle): MemArena here is temporary until we incorporate AttackSpec to
@ -1320,6 +1300,7 @@ INTERNAL void beginAttack(EventQueue *eventQueue, World *world,
INTERNAL void endAttack(MemoryArena *arena, EventQueue *eventQueue, INTERNAL void endAttack(MemoryArena *arena, EventQueue *eventQueue,
World *world, Entity *attacker) World *world, Entity *attacker)
{ {
#if 0
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
ASSERT(attacker->stats->entityIdToAttack != ENTITY_NULL_ID); ASSERT(attacker->stats->entityIdToAttack != ENTITY_NULL_ID);
#endif #endif
@ -1398,6 +1379,7 @@ INTERNAL void endAttack(MemoryArena *arena, EventQueue *eventQueue,
entityStateSwitch(eventQueue, world, attacker, entitystate_idle); entityStateSwitch(eventQueue, world, attacker, entitystate_idle);
} }
} }
#endif
} }
INTERNAL void sortWorldEntityList(World *world) INTERNAL void sortWorldEntityList(World *world)
@ -1735,12 +1717,12 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
hero->dPos = V2(0.0f, 0.0f); hero->dPos = V2(0.0f, 0.0f);
if (hero->currAnimId == animlist_hero_walk) if (hero->currAnimId == animlist_hero_walk)
{ {
entity_setActiveAnim(hero, animlist_hero_idle); entity_setActiveAnim(hero, "Claude_idle");
} }
} }
else if (hero->currAnimId == animlist_hero_idle) else if (hero->currAnimId == animlist_hero_idle)
{ {
entity_setActiveAnim(hero, animlist_hero_walk); entity_setActiveAnim(hero, "Claude_idle");
} }
f32 heroSpeed = 6.2f * METERS_TO_PIXEL; f32 heroSpeed = 6.2f * METERS_TO_PIXEL;
@ -1978,7 +1960,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
{ {
hero->state = entitystate_idle; hero->state = entitystate_idle;
world->entityIdInBattle[hero->id] = FALSE; world->entityIdInBattle[hero->id] = FALSE;
entity_setActiveAnim(hero, animlist_hero_idle); entity_setActiveAnim(hero, "Claude_idle");
} }
hero->stats->entityIdToAttack = -1; hero->stats->entityIdToAttack = -1;
hero->stats->actionTimer = hero->stats->actionRate; hero->stats->actionTimer = hero->stats->actionRate;
@ -2058,6 +2040,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
state->uiState.keyChar = keycode_null; state->uiState.keyChar = keycode_null;
/* Draw hero avatar */ /* Draw hero avatar */
#if 0
TexAtlas *heroAtlas = asset_getTextureAtlas(assetManager, texlist_hero); TexAtlas *heroAtlas = asset_getTextureAtlas(assetManager, texlist_hero);
v4 heroAvatarTexRect = heroAtlas->texRect[herorects_head]; v4 heroAvatarTexRect = heroAtlas->texRect[herorects_head];
v2 heroAvatarSize = math_getRectSize(heroAvatarTexRect); v2 heroAvatarSize = math_getRectSize(heroAvatarTexRect);
@ -2079,6 +2062,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
renderer_staticString(&state->renderer, &state->arena, font, heroAvatarStr, renderer_staticString(&state->renderer, &state->arena, font, heroAvatarStr,
strPos, V2(0, 0), 0, V4(0, 0, 1, 1)); strPos, V2(0, 0), 0, V4(0, 0, 1, 1));
#endif
for (i32 i = 0; i < world->maxEntities; i++) for (i32 i = 0; i < world->maxEntities; i++)
{ {

View File

@ -11,25 +11,33 @@ typedef struct MemoryArena MemoryArena;
typedef struct AssetManager typedef struct AssetManager
{ {
Texture textures[32]; Texture textures[32];
TexAtlas texAtlas[32];
Shader shaders[32]; Shader shaders[32];
Animation anims[32]; Animation anims[1024];
AudioVorbis audio[32]; AudioVorbis audio[32];
Font font; Font font;
TexAtlasEntry texAtlas_[8]; TexAtlas texAtlas[8];
} AssetManager; } AssetManager;
#define MAX_TEXTURE_SIZE 1024 #define MAX_TEXTURE_SIZE 1024
Rect asset_getAtlasSubTexRect(TexAtlas *atlas, char *key);
AudioVorbis *asset_getVorbis(AssetManager *assetManager, AudioVorbis *asset_getVorbis(AssetManager *assetManager,
const enum AudioList type); const enum AudioList type);
Texture *asset_getTexture(AssetManager *const assetManager, Texture *asset_getTexture(AssetManager *const assetManager,
const enum TexList type); const enum TexList type);
TexAtlas *asset_makeTexAtlas(AssetManager *const assetManager,
MemoryArena *arena, const char *const key);
Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type); Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type);
TexAtlas *asset_getTextureAtlas(AssetManager *assetManager,
const enum TexList type); TexAtlas *asset_getTexAtlas(AssetManager *const assetManager,
Animation *asset_getAnim(AssetManager *assetManager, i32 type); const char *const key);
Animation *asset_getAnim(AssetManager *assetManager, char *key);
const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena, const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena,
const char *const path, const enum AudioList type); const char *const path, const enum AudioList type);
@ -46,8 +54,8 @@ const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena,
const char *filePath); const char *filePath);
void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena, void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena,
i32 texId, i32 animId, i32 *atlasIndexes, i32 numFrames, char *animName, TexAtlas *atlas, char **subTextureNames,
f32 frameDuration); i32 numSubTextures, f32 frameDuration);
v2 asset_stringDimInPixels(const Font *const font, const char *const string); v2 asset_stringDimInPixels(const Font *const font, const char *const string);

View File

@ -83,36 +83,37 @@ typedef struct AudioVorbis
typedef struct AtlasSubTexture typedef struct AtlasSubTexture
{ {
char *name; // NOTE(doyle): Key used to arrive to hash entry
char *key;
Rect rect; Rect rect;
// NOTE(doyle): For hashing collisions // NOTE(doyle): For hashing collisions
struct AtlasSubTexture *next; struct AtlasSubTexture *next;
} AtlasSubTexture; } 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 char *key;
v4 texRect[128]; Texture *tex;
AtlasSubTexture subTex[512];
struct TexAtlas *next;
} TexAtlas; } TexAtlas;
typedef struct Animation typedef struct Animation
{ {
union {
char *name;
char *key;
};
TexAtlas *atlas; TexAtlas *atlas;
i32 *frameIndex; char **frameList;
i32 numFrames; i32 numFrames;
f32 frameDuration; f32 frameDuration;
struct Animation *next;
} Animation; } Animation;
// TODO(doyle): We only use the offset and advance metric at the moment, remove? // TODO(doyle): We only use the offset and advance metric at the moment, remove?
@ -136,8 +137,6 @@ typedef struct CharMetrics
typedef struct Font typedef struct Font
{ {
TexAtlas *atlas; TexAtlas *atlas;
Texture *tex;
FontMetrics metrics; FontMetrics metrics;
// NOTE(doyle): Array of character's by ASCII value starting from // NOTE(doyle): Array of character's by ASCII value starting from

View File

@ -54,4 +54,14 @@ i32 common_atoi(const char *string, const i32 len);
// machines. // machines.
u32 common_murmurHash2(const void *key, i32 len, u32 seed); u32 common_murmurHash2(const void *key, i32 len, u32 seed);
// TODO(doyle): Use a proper random seed
#define RANDOM_SEED 0xDEADBEEF
inline u32 common_getHashIndex(const char *const key, u32 tableSize)
{
u32 result = common_murmurHash2(key, common_strlen(key), RANDOM_SEED);
result = result % tableSize;
return result;
}
#endif #endif

View File

@ -63,12 +63,12 @@ typedef struct EntityStats
enum EntityAttack queuedAttack; enum EntityAttack queuedAttack;
} EntityStats; } EntityStats;
typedef struct EntityAnim_ typedef struct EntityAnim
{ {
Animation *anim; Animation *anim;
i32 currFrame; i32 currFrame;
f32 currDuration; f32 currDuration;
} EntityAnim_; } EntityAnim;
typedef struct Entity typedef struct Entity
{ {
@ -86,22 +86,17 @@ typedef struct Entity
Texture *tex; Texture *tex;
b32 collides; b32 collides;
// TODO(doyle): String based access EntityAnim animList[16];
// TODO(doyle): Separate animation refs from curr animation state, entity i32 currAnimId;
// only ever has one active current animation. ATM every entity animation
// has a currframe and duration .. either that or we stop resetting
// animation on entity context switch
EntityAnim_ anim[16];
enum AnimList currAnimId;
EntityStats *stats; EntityStats *stats;
AudioRenderer *audioRenderer; AudioRenderer *audioRenderer;
i32 numAudioRenderers; i32 numAudioRenderers;
} Entity; } Entity;
void entity_setActiveAnim(Entity *entity, enum AnimList animId); void entity_setActiveAnim(Entity *entity, char *animName);
void entity_updateAnim(Entity *entity, f32 dt); void entity_updateAnim(Entity *entity, f32 dt);
void entity_addAnim(AssetManager *assetManager, Entity *entity, i32 animId); void entity_addAnim(AssetManager *assetManager, Entity *entity, char *animName);
void entity_addGenericMob(MemoryArena *arena, AssetManager *assetManager, void entity_addGenericMob(MemoryArena *arena, AssetManager *assetManager,
World *world, v2 pos); World *world, v2 pos);
Entity *entity_add(MemoryArena *arena, World *world, v2 pos, v2 size, Entity *entity_add(MemoryArena *arena, World *world, v2 pos, v2 size,

View File

@ -25,6 +25,8 @@ typedef struct Renderer
typedef struct RenderTex typedef struct RenderTex
{ {
Texture *tex; Texture *tex;
// TODO(doyle): Switch to rect
v4 texRect; v4 texRect;
} RenderTex; } RenderTex;