diff --git a/Data/Textures/atlas.png b/Data/Textures/atlas.png index 4f09e1e..00dc4dd 100644 --- a/Data/Textures/atlas.png +++ b/Data/Textures/atlas.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc1c98690a7b2f0811e85194a418518bc57dd61f998cc17b1e4c11e3633a5e1c -size 8374839 +oid sha256:ee13563e7a48cb1d8688709ecfcc8677e5a80c139351fd47bad3277a6714937d +size 8366517 diff --git a/Data/Textures/atlas.txt b/Data/Textures/atlas.txt index 94c0f15..32870bd 100644 --- a/Data/Textures/atlas.txt +++ b/Data/Textures/atlas.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4513308644f4da73dde6ea59d1b3ccf1f6d3e109ad04ce0f9b2b0cc9215d8ec9 -size 5915 +oid sha256:842eaba01f0bf9d25bca4cf4c501877373fa9eaecbfecc30f0bc52a9b2e93b93 +size 10672 diff --git a/External/strnatcmp.c b/External/strnatcmp.c new file mode 100644 index 0000000..ecec4d7 --- /dev/null +++ b/External/strnatcmp.c @@ -0,0 +1,177 @@ +/* -*- mode: c; c-file-style: "k&r" -*- + + strnatcmp.c -- Perform 'natural order' comparisons of strings in C. + Copyright (C) 2000, 2004 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + + +/* partial change history: + * + * 2004-10-10 mbp: Lift out character type dependencies into macros. + * + * Eric Sosman pointed out that ctype functions take a parameter whose + * value must be that of an unsigned int, even on platforms that have + * negative chars in their default char type. + */ + +#include /* size_t */ +#include + +#include "strnatcmp.h" + + +/* These are defined as macros to make it easier to adapt this code to + * different characters types or comparison functions. */ +static inline int +nat_isdigit(nat_char a) +{ + return isdigit((unsigned char) a); +} + + +static inline int +nat_isspace(nat_char a) +{ + return isspace((unsigned char) a); +} + + +static inline nat_char +nat_toupper(nat_char a) +{ + return toupper((unsigned char) a); +} + + +static int +compare_right(nat_char const *a, nat_char const *b) +{ + int bias = 0; + + /* The longest run of digits wins. That aside, the greatest + value wins, but we can't know that it will until we've scanned + both numbers to know that they have the same magnitude, so we + remember it in BIAS. */ + for (;; a++, b++) { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) + return bias; + if (!nat_isdigit(*a)) + return -1; + if (!nat_isdigit(*b)) + return +1; + if (*a < *b) { + if (!bias) + bias = -1; + } else if (*a > *b) { + if (!bias) + bias = +1; + } else if (!*a && !*b) + return bias; + } + + return 0; +} + + +static int +compare_left(nat_char const *a, nat_char const *b) +{ + /* Compare two left-aligned numbers: the first to have a + different value wins. */ + for (;; a++, b++) { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) + return 0; + if (!nat_isdigit(*a)) + return -1; + if (!nat_isdigit(*b)) + return +1; + if (*a < *b) + return -1; + if (*a > *b) + return +1; + } + + return 0; +} + + +static int +strnatcmp0(nat_char const *a, nat_char const *b, int fold_case) +{ + int ai, bi; + nat_char ca, cb; + int fractional, result; + + ai = bi = 0; + while (1) { + ca = a[ai]; cb = b[bi]; + + /* skip over leading spaces or zeros */ + while (nat_isspace(ca)) + ca = a[++ai]; + + while (nat_isspace(cb)) + cb = b[++bi]; + + /* process run of digits */ + if (nat_isdigit(ca) && nat_isdigit(cb)) { + fractional = (ca == '0' || cb == '0'); + + if (fractional) { + if ((result = compare_left(a+ai, b+bi)) != 0) + return result; + } else { + if ((result = compare_right(a+ai, b+bi)) != 0) + return result; + } + } + + if (!ca && !cb) { + /* The strings compare the same. Perhaps the caller + will want to call strcmp to break the tie. */ + return 0; + } + + if (fold_case) { + ca = nat_toupper(ca); + cb = nat_toupper(cb); + } + + if (ca < cb) + return -1; + + if (ca > cb) + return +1; + + ++ai; ++bi; + } +} + + +int +strnatcmp(nat_char const *a, nat_char const *b) { + return strnatcmp0(a, b, 0); +} + + +/* Compare, recognizing numeric string and ignoring case. */ +int +strnatcasecmp(nat_char const *a, nat_char const *b) { + return strnatcmp0(a, b, 1); +} diff --git a/External/strnatcmp.h b/External/strnatcmp.h new file mode 100644 index 0000000..3930874 --- /dev/null +++ b/External/strnatcmp.h @@ -0,0 +1,38 @@ +/* -*- mode: c; c-file-style: "k&r" -*- + + strnatcmp.c -- Perform 'natural order' comparisons of strings in C. + Copyright (C) 2000, 2004 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* CUSTOMIZATION SECTION + * + * You can change this typedef, but must then also change the inline + * functions in strnatcmp.c */ +typedef char nat_char; + +int strnatcmp(nat_char const *a, nat_char const *b); +int strnatcasecmp(nat_char const *a, nat_char const *b); + +#ifdef __cplusplus +} +#endif diff --git a/External/tely b/External/tely index f4ab2fb..c63954d 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit f4ab2fbd147623ac38be6f92fedf0e0049aeafb5 +Subproject commit c63954d31b0c99203c5dc752494bbaefd59128f0 diff --git a/feely_pona.cpp b/feely_pona.cpp index ae4a44f..5c22d1f 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -102,7 +102,7 @@ TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, TELY_A anim->ms_per_frame = DQN_CAST(uint32_t)(1000.f / frames_per_second.value); DQN_ASSERT(anim->ms_per_frame != 0); } else { - DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for sprite frame lines"); + DQN_ASSERTF(line_splits.size == 5, "Expected 5 splits for sprite frame lines"); Dqn_String8ToU64Result x = Dqn_String8_ToU64(line_splits.data[0], 0); Dqn_String8ToU64Result y = Dqn_String8_ToU64(line_splits.data[1], 0); Dqn_String8ToU64Result w = Dqn_String8_ToU64(line_splits.data[2], 0); @@ -2222,16 +2222,22 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) // NOTE: Render entity sprites ============================================================= if (entity->action.sprite.anim) { - FP_GameEntityAction const *action = &entity->action; - TELY_AssetAnimatedSprite const sprite = action->sprite; + FP_GameEntityAction const *action = &entity->action; + TELY_AssetAnimatedSprite const sprite = action->sprite; + uint64_t const elapsed_ms = game->clock_ms - action->started_at_clock_ms; + uint16_t const raw_anim_frame = DQN_CAST(uint16_t)(elapsed_ms / sprite.anim->ms_per_frame); + DQN_ASSERTF(sprite.anim->count, "We will modulo by 0 or overflow to UINT64_MAX"); + + // TODO(doyle): So many ways to create and get sprite data .. its a mess + // I want to override per sprite anim height, we currently use the one + // in the entity which is not correct. + FP_EntityRenderData render_data = FP_Entity_GetRenderData(game, entity->type, action->state, entity->direction); - uint64_t elapsed_ms = game->clock_ms - action->started_at_clock_ms; uint16_t anim_frame = 0; - if (action->sprite_play_once) { - anim_frame = DQN_MIN(DQN_CAST(uint16_t)(elapsed_ms / sprite.anim->ms_per_frame), sprite.anim->count); - } else { - anim_frame = DQN_CAST(uint16_t)(elapsed_ms / sprite.anim->ms_per_frame) % sprite.anim->count; - } + if (action->sprite_play_once) + anim_frame = DQN_MIN(raw_anim_frame, (sprite.anim->count - 1)); + else + anim_frame = raw_anim_frame % sprite.anim->count; Dqn_usize sprite_index = sprite.anim->index + anim_frame; Dqn_Rect src_rect = {}; @@ -2252,7 +2258,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) } Dqn_f32 sprite_in_meters = FP_Game_PixelsToMetersNx1(game, src_rect.size.y); - Dqn_f32 size_scale = entity->sprite_height.meters / sprite_in_meters; + Dqn_f32 size_scale = render_data.height.meters / sprite_in_meters; Dqn_Rect dest_rect = {}; dest_rect.size = src_rect.size * size_scale; @@ -2796,8 +2802,8 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) phone_icon_rect.pos = Dqn_V2_InitNx2(next_pos.x - phone_icon_rect.size.x * .25f, next_pos.y - (phone_icon_rect.size.y * .35f)); } - Dqn_f32 pixels_per_kb = 15.5f; - Dqn_f32 bar_width = DQN_CAST(Dqn_f32)player->terry_mobile_data_plan_cap / DQN_KILOBYTES(1) * pixels_per_kb; + Dqn_f32 pixels_per_kb = 15.5f; + Dqn_f32 bar_width = DQN_CAST(Dqn_f32)player->terry_mobile_data_plan_cap / DQN_KILOBYTES(1) * pixels_per_kb; Dqn_f32 bar_x = next_pos.x + (phone_icon_rect.size.x * .25f); Dqn_f32 data_plan_t = player->terry_mobile_data_plan / DQN_CAST(Dqn_f32)player->terry_mobile_data_plan_cap; diff --git a/feely_pona_entity_create.cpp b/feely_pona_entity_create.cpp index bef87ae..d0a612f 100644 --- a/feely_pona_entity_create.cpp +++ b/feely_pona_entity_create.cpp @@ -261,7 +261,10 @@ FP_EntityRenderData FP_Entity_GetRenderData(FP_Game *game, FP_EntityType type, u switch (state) { case FP_EntityMobSpawnerState_Nil: break; case FP_EntityMobSpawnerState_Idle: result.anim_name = g_anim_names.portal; break; - case FP_EntityMobSpawnerState_Shutdown: result.anim_name = g_anim_names.portal_break; break; + case FP_EntityMobSpawnerState_Shutdown: { + result.anim_name = g_anim_names.portal_break; + result.height.meters = 3.5f; + } break; } } break; diff --git a/feely_pona_sprite_packer.cpp b/feely_pona_sprite_packer.cpp index 63fed97..7edfec2 100644 --- a/feely_pona_sprite_packer.cpp +++ b/feely_pona_sprite_packer.cpp @@ -24,6 +24,13 @@ DQN_MSVC_WARNING_DISABLE(4244) // warning C4244: 'argument': conversion from 'in #include "External/tely/external/stb/stb_image.h" DQN_MSVC_WARNING_POP +DQN_MSVC_WARNING_PUSH +DQN_MSVC_WARNING_DISABLE(4244) // External\strnatcmp.c|58| warning C4244: 'return': conversion from 'int' to 'nat_char', possible loss of data +DQN_MSVC_WARNING_DISABLE(4702) // External\strnatcmp.c|110 warning| C4702: unreachable code +#include "External/strnatcmp.h" +#include "External/strnatcmp.c" +DQN_MSVC_WARNING_POP + struct SpriteSpecification { uint16_t frame_count; @@ -123,24 +130,44 @@ int main(int argc, char const *argv[]) } // NOTE: Get the list of files ================================================================= - Dqn_List file_list = Dqn_List_Init(scratch.arena, 128); + Dqn_List file_list_raw = Dqn_List_Init(scratch.arena, 128); for (Dqn_Win_FolderIterator it = {}; Dqn_Win_FolderIterate(dir, &it); ) { if (Dqn_String8_EndsWithInsensitive(it.file_name, DQN_STRING8(".png"))) { - Dqn_String8 *item = Dqn_List_Make(&file_list, Dqn_ZeroMem_Yes); + Dqn_String8 *item = Dqn_List_Make(&file_list_raw, Dqn_ZeroMem_Yes); *item = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s", DQN_STRING_FMT(dir), DQN_STRING_FMT(it.file_name)); } } + // NOTE: Sort the list of files ================================================================ + if (file_list_raw.count == 0) { + Dqn_Log_InfoF("There are no '.png' files in the directory '%.*s' to create an atlas from, exiting", DQN_STRING_FMT(dir)); + return 0; + } + + // NOTE: Bubble sort the file list naturally =================================================== + Dqn_Slice file_list = Dqn_List_ToSliceCopy(&file_list_raw, scratch.arena); + for (bool swapped = true; swapped; ) { + swapped = false; + for (Dqn_usize list_index = 0; list_index < file_list.size - 1; list_index++) { + Dqn_String8 left = file_list.data[list_index + 0]; + Dqn_String8 right = file_list.data[list_index + 1]; + if (strnatcmp(left.data, right.data) > 0) { + DQN_SWAP(file_list.data[list_index + 0], file_list.data[list_index + 1]); + swapped = true; + } + } + } + // NOTE: Setup the rect-pack state ============================================================= - stbrp_node *nodes = Dqn_Arena_NewArray(scratch.arena, stbrp_node, file_list.count, Dqn_ZeroMem_Yes); + stbrp_node *nodes = Dqn_Arena_NewArray(scratch.arena, stbrp_node, file_list.size, Dqn_ZeroMem_Yes); stbrp_context pack_context = {}; - stbrp_init_target(&pack_context, atlas_size.w, atlas_size.h, nodes, DQN_CAST(int)file_list.count); + stbrp_init_target(&pack_context, atlas_size.w, atlas_size.h, nodes, DQN_CAST(int)file_list.size); // NOTE: Load the sprites to determine their dimensions for rect packing ======================= - Dqn_SArray rects = Dqn_SArray_Init(scratch.arena, file_list.count, Dqn_ZeroMem_Yes); - for (Dqn_ListIterator it = {}; Dqn_List_Iterate(&file_list, &it, 0);) { + Dqn_SArray rects = Dqn_SArray_Init(scratch.arena, file_list.size, Dqn_ZeroMem_Yes); + for (Dqn_String8 it : file_list) { int x = 0, y = 0, channels_in_file = 0; - stbi_uc *pixels = stbi_load(it.data->data, &x, &y, &channels_in_file, 4 /*desired_channels*/); + stbi_uc *pixels = stbi_load(it.data, &x, &y, &channels_in_file, 4 /*desired_channels*/); DQN_ASSERT(pixels); stbi_image_free(pixels); @@ -150,7 +177,15 @@ int main(int argc, char const *argv[]) rect->h = y; // NOTE: Enumerate the number of frames for this animation ================================= - Dqn_String8 anim_prefix = SpriteAnimNameFromFilePath(*it.data); + Dqn_String8 anim_prefix = SpriteAnimNameFromFilePath(it); + Dqn_String8 file_name = Dqn_String8_FileNameFromPath(it); + DQN_ASSERTF(!Dqn_String8_HasChar(file_name, ';'), + "\n\nSprite frame loaded from file\n" + " '%.*s'\n" + "\n" + "however the file name has a semicolon which is not supported because we use a semicolon to delimit our sprite specification", + DQN_STRING_FMT(file_name)); + Dqn_DSMapResult slot = Dqn_DSMap_FindKeyString8(&sprite_spec_table, anim_prefix); DQN_ASSERTF(slot.found, "\n\nSprite frame loaded from file\n" @@ -161,12 +196,12 @@ int main(int argc, char const *argv[]) "\n" "Add a line in format of ; to the file, e.g.\n" " %.*s;8\n", - DQN_STRING_FMT(*it.data), + DQN_STRING_FMT(it), DQN_STRING_FMT(sprite_spec_path), DQN_STRING_FMT(anim_prefix)); slot.value->frame_count++; - Dqn_Log_InfoF("Packing sprite: %.*s", DQN_STRING_FMT(*it.data)); + Dqn_Log_InfoF("Packing sprite: %.*s", DQN_STRING_FMT(it)); } // NOTE: Pack the rects ======================================================================== @@ -174,7 +209,7 @@ int main(int argc, char const *argv[]) Dqn_Log_ErrorF("STB rect pack failed to pack font rects into rectangle [width=%d, height=%d, num_rects=%d]", atlas_size.w, atlas_size.h, - file_list.count); + file_list.size); return -1; } @@ -206,11 +241,12 @@ int main(int argc, char const *argv[]) Dqn_Log_InfoF("Generating meta file: %.*s", DQN_STRING_FMT(meta_path)); Dqn_String8 active_anim_prefix = {}; - for (Dqn_ListIterator it = {}; Dqn_List_Iterate(&file_list, &it, 0);) { + DQN_FOR_UINDEX (file_list_index, file_list.size) { + Dqn_String8 it = file_list.data[file_list_index]; int w, h, channels_in_file; - stbi_uc *loaded_image = stbi_load(it.data->data, &w, &h, &channels_in_file, 4 /*desired_channels*/); + stbi_uc *loaded_image = stbi_load(it.data, &w, &h, &channels_in_file, 4 /*desired_channels*/); - stbrp_rect packed_rect = rects.data[it.index]; + stbrp_rect packed_rect = rects.data[file_list_index]; char *src = DQN_CAST(char *)loaded_image; // NOTE: Generate the image ================================================================ @@ -227,7 +263,7 @@ int main(int argc, char const *argv[]) stbi_image_free(loaded_image); // NOTE: Detect what animation we are currently processing ================================= - Dqn_String8 anim_prefix = SpriteAnimNameFromFilePath(*it.data); + Dqn_String8 anim_prefix = SpriteAnimNameFromFilePath(it); if (active_anim_prefix.size == 0 || active_anim_prefix != anim_prefix) { // NOTE: Anim prefix is different, we are starting a new animation- mark it accordingly active_anim_prefix = anim_prefix; @@ -236,8 +272,9 @@ int main(int argc, char const *argv[]) } // NOTE: Write the sprite rectangles in ==================================================== - Dqn_Fs_WriteFileF(&meta_file, "%d;%d;%d;%d", packed_rect.x, packed_rect.y, packed_rect.w, packed_rect.h); - if (it.index != (file_list.count - 1)) + Dqn_String8 file_name = Dqn_String8_FileNameFromPath(it); + Dqn_Fs_WriteFileF(&meta_file, "%d;%d;%d;%d;%.*s", packed_rect.x, packed_rect.y, packed_rect.w, packed_rect.h, DQN_STRING_FMT(file_name)); + if (file_list_index != (file_list.size - 1)) Dqn_Fs_WriteFileF(&meta_file, "\n"); } diff --git a/project.rdbg b/project.rdbg index 2f87f4b..d83174a 100644 Binary files a/project.rdbg and b/project.rdbg differ