From 1ff0daba4a63434832449a6355c9dca3ddf4fcf0 Mon Sep 17 00:00:00 2001 From: doyle Date: Sat, 23 Sep 2023 21:21:08 +1000 Subject: [PATCH] tely: Add idle animation for the player --- Data/Textures/protagonist_walk.png | 3 + Data/Textures/protagonist_walk.txt | 3 + .../protagonist_walk/Idle_0000s_0000_8.png | 3 + .../protagonist_walk/Idle_0000s_0001_7.png | 3 + .../protagonist_walk/Idle_0000s_0002_6.png | 3 + .../protagonist_walk/Idle_0000s_0003_5.png | 3 + .../protagonist_walk/Idle_0000s_0004_4.png | 3 + .../protagonist_walk/Idle_0000s_0005_3.png | 3 + .../protagonist_walk/Idle_0000s_0006_2.png | 3 + .../protagonist_walk/Idle_0000s_0007_1.png | 3 + External/tely | 2 +- build.bat | 5 + feely_pona.cpp | 98 ++++++++++++-- feely_pona_game.h | 3 + feely_pona_sprite_packer.cpp | 120 ++++++++++++++++++ project.rdbg | Bin 236 -> 1151 bytes 16 files changed, 244 insertions(+), 14 deletions(-) create mode 100644 Data/Textures/protagonist_walk.png create mode 100644 Data/Textures/protagonist_walk.txt create mode 100644 Data/Textures/protagonist_walk/Idle_0000s_0000_8.png create mode 100644 Data/Textures/protagonist_walk/Idle_0000s_0001_7.png create mode 100644 Data/Textures/protagonist_walk/Idle_0000s_0002_6.png create mode 100644 Data/Textures/protagonist_walk/Idle_0000s_0003_5.png create mode 100644 Data/Textures/protagonist_walk/Idle_0000s_0004_4.png create mode 100644 Data/Textures/protagonist_walk/Idle_0000s_0005_3.png create mode 100644 Data/Textures/protagonist_walk/Idle_0000s_0006_2.png create mode 100644 Data/Textures/protagonist_walk/Idle_0000s_0007_1.png create mode 100644 feely_pona_sprite_packer.cpp diff --git a/Data/Textures/protagonist_walk.png b/Data/Textures/protagonist_walk.png new file mode 100644 index 0000000..4755cf2 --- /dev/null +++ b/Data/Textures/protagonist_walk.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9762cbc2c610babb2a5cf783d4261f14ab836cf3eb104e3d2bdcaa27bc2912c +size 320173 diff --git a/Data/Textures/protagonist_walk.txt b/Data/Textures/protagonist_walk.txt new file mode 100644 index 0000000..39c5e7a --- /dev/null +++ b/Data/Textures/protagonist_walk.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:683ca283bc31ed1b21c7a3012f85f58d2c2e0dd6ec929c1981a6a08dd89f4702 +size 254 diff --git a/Data/Textures/protagonist_walk/Idle_0000s_0000_8.png b/Data/Textures/protagonist_walk/Idle_0000s_0000_8.png new file mode 100644 index 0000000..0040a7d --- /dev/null +++ b/Data/Textures/protagonist_walk/Idle_0000s_0000_8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7c8d85e62da44fe9cddae1a95d74d5246f2a48b22aa8b885ca90bd99c994646 +size 20510 diff --git a/Data/Textures/protagonist_walk/Idle_0000s_0001_7.png b/Data/Textures/protagonist_walk/Idle_0000s_0001_7.png new file mode 100644 index 0000000..7bc70ba --- /dev/null +++ b/Data/Textures/protagonist_walk/Idle_0000s_0001_7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0102a48022e7e4c4781ef6254b43306829dbca373632e382b483608195c247ca +size 20290 diff --git a/Data/Textures/protagonist_walk/Idle_0000s_0002_6.png b/Data/Textures/protagonist_walk/Idle_0000s_0002_6.png new file mode 100644 index 0000000..2824ae8 --- /dev/null +++ b/Data/Textures/protagonist_walk/Idle_0000s_0002_6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e71283b47f283d534c3621751a20bb34fa9f2e4143a56d1e06f739794cb30f4a +size 20622 diff --git a/Data/Textures/protagonist_walk/Idle_0000s_0003_5.png b/Data/Textures/protagonist_walk/Idle_0000s_0003_5.png new file mode 100644 index 0000000..9c9a30e --- /dev/null +++ b/Data/Textures/protagonist_walk/Idle_0000s_0003_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73e71dfc823d151a75024ac6febb7d5a088d78aa61501822a9919d58ef03b99f +size 20972 diff --git a/Data/Textures/protagonist_walk/Idle_0000s_0004_4.png b/Data/Textures/protagonist_walk/Idle_0000s_0004_4.png new file mode 100644 index 0000000..badf47a --- /dev/null +++ b/Data/Textures/protagonist_walk/Idle_0000s_0004_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:624373db86eb9bc4f5ca7b89150a72f73dfad2efb277ff5ede4a4b94e29c9cfe +size 20771 diff --git a/Data/Textures/protagonist_walk/Idle_0000s_0005_3.png b/Data/Textures/protagonist_walk/Idle_0000s_0005_3.png new file mode 100644 index 0000000..78ccca6 --- /dev/null +++ b/Data/Textures/protagonist_walk/Idle_0000s_0005_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fc090ee6fb60401c2959bc5c0a685744a562f632d246c8fa0bbc7702df13dfa +size 20387 diff --git a/Data/Textures/protagonist_walk/Idle_0000s_0006_2.png b/Data/Textures/protagonist_walk/Idle_0000s_0006_2.png new file mode 100644 index 0000000..8d80f4c --- /dev/null +++ b/Data/Textures/protagonist_walk/Idle_0000s_0006_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c76ba09559f80f97abd48018da206ca95437a554c0434f571c471bf261335e13 +size 21234 diff --git a/Data/Textures/protagonist_walk/Idle_0000s_0007_1.png b/Data/Textures/protagonist_walk/Idle_0000s_0007_1.png new file mode 100644 index 0000000..2ffe3ab --- /dev/null +++ b/Data/Textures/protagonist_walk/Idle_0000s_0007_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4289f09938e7c6c32d1b5d721e725c08019f16ffc222465a4f73ff72d7af302 +size 20908 diff --git a/External/tely b/External/tely index 5a56d52..e245208 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit 5a56d52e71c4b66a9c6fa795a5a851f0dd4206cb +Subproject commit e245208bc9b7140eb668cd2caef80c40b2b50836 diff --git a/build.bat b/build.bat index bff5653..7700d1a 100644 --- a/build.bat +++ b/build.bat @@ -82,6 +82,11 @@ set clang_dll_cmd=clang-cl %dll_compile_flags% /Fotely_dll_clang /Fetely_dll_c REM MSVC build ===================================================================================== +if not exist "%build_dir%\feely_pona_sprite_packer" ( + set msvc_sprite_packer_cmd=cl %common_compile_flags% %code_dir%\feely_pona_sprite_packer.cpp /Fofeely_pona_sprite_packer /Fefeely_pona_sprite_packer %common_link_flags% + call powershell -Command "$duration = Measure-Command {%msvc_sprite_packer_cmd% | Out-Default}; Write-Host 'msvc (sprite packer):' $duration.TotalSeconds 'seconds'" +) + set msvc_build_platform=$platform_time = Measure-Command {%msvc_cmd% ^| Out-Default}; set msvc_build_dll=$dll_time = Measure-Command {%msvc_dll_cmd% ^| Out-Default}; set msvc_print_dll_time=Write-Host 'msvc dll:'$dll_time.TotalSeconds's' diff --git a/feely_pona.cpp b/feely_pona.cpp index 607b13a..7724dc0 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -398,6 +398,68 @@ void TELY_DLL_Init(void *user_data) DQN_MEMCPY(game->hero_sprite_anims.data, &hero_anims, sizeof(hero_anims[0]) * DQN_ARRAY_UCOUNT(hero_anims)); } + { + TELY_AssetSpriteSheet *sheet = &game->protag_walk_sprite_sheet; + Dqn_Slice *anims = &game->protag_walk_sprite_anims; + + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8 sheet_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/protagonist_walk.png", DQN_STRING_FMT(assets->textures_dir)); + sheet->tex_handle = platform->func_load_texture(assets, DQN_STRING8("Protagonist Walk"), sheet_path); + sheet->sprite_count = 28; + sheet->sprites_per_row = 7; + sheet->sprite_size = Dqn_V2I_InitNx2(185, 170); + sheet->type = TELY_AssetSpriteSheetType_Rects; + + struct LoadedSprite + { + Dqn_String8 name; + Dqn_Rect rect; + }; + + // NOTE: Load the sprite meta file ========================================================= + { + Dqn_List raw_sprites = Dqn_List_Init(scratch.arena, 32); + Dqn_String8 sheet_meta_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/protagonist_walk.txt", DQN_STRING_FMT(assets->textures_dir)); + Dqn_String8 sheet_meta_file = Dqn_Fs_Read(sheet_meta_path, scratch.allocator); + + Dqn_String8BinarySplitResult split = {}; + Dqn_String8 first = sheet_meta_file; + do { + split = Dqn_String8_BinarySplit(first, DQN_STRING8("\n")); + Dqn_String8BinarySplitResult name = Dqn_String8_BinarySplit(split.lhs, DQN_STRING8(";")); + Dqn_String8BinarySplitResult x = Dqn_String8_BinarySplit(name.rhs, DQN_STRING8(";")); + Dqn_String8BinarySplitResult y = Dqn_String8_BinarySplit(x.rhs, DQN_STRING8(";")); + Dqn_String8BinarySplitResult width = Dqn_String8_BinarySplit(y.rhs, DQN_STRING8(";")); + Dqn_String8BinarySplitResult height = Dqn_String8_BinarySplit(width.rhs, DQN_STRING8(";")); + + LoadedSprite *loaded_anim = Dqn_List_Make(&raw_sprites, 1); + loaded_anim->name = name.lhs; + loaded_anim->rect.pos.x = DQN_CAST(Dqn_f32)Dqn_String8_ToU64(x.lhs, 0).value; + loaded_anim->rect.pos.y = DQN_CAST(Dqn_f32)Dqn_String8_ToU64(y.lhs, 0).value; + loaded_anim->rect.size.w = DQN_CAST(Dqn_f32)Dqn_String8_ToU64(width.lhs, 0).value; + loaded_anim->rect.size.h = DQN_CAST(Dqn_f32)Dqn_String8_ToU64(height.lhs, 0).value; + + first = split.rhs; + } while (first.size); + + // NOTE: Populate the sheet ============================================================ + sheet->rects = Dqn_Slice_Alloc(&platform->arena, raw_sprites.count, Dqn_ZeroMem_No); + for (Dqn_ListIterator it = {}; Dqn_List_Iterate(&raw_sprites, &it, 0); ) { + sheet->rects.data[it.index] = it.data->rect; + } + } + + TELY_AssetSpriteAnimation raw_anims[] = { + // {DQN_STRING8("Walk down"), /*index*/ 0, /*count*/ 7, /*seconds_per_frame*/ 1 / 8.f}, + {DQN_STRING8("Walk idle"), /*index*/ 0, /*count*/ 8, /*seconds_per_frame*/ 1 / 8.f}, + // {DQN_STRING8("Walk left"), /*index*/ 15, /*count*/ 7, /*seconds_per_frame*/ 1 / 8.f}, + // {DQN_STRING8("Walk right"), /*index*/ 22, /*count*/ 7, /*seconds_per_frame*/ 1 / 8.f}, + }; + + *anims = Dqn_Slice_Alloc(&platform->arena, DQN_ARRAY_UCOUNT(raw_anims), Dqn_ZeroMem_No); + DQN_MEMCPY(anims->data, &raw_anims, sizeof(raw_anims[0]) * DQN_ARRAY_UCOUNT(raw_anims)); + } + game->entities = Dqn_VArray_Init(&platform->arena, 1024 * 8); game->root_entity = Dqn_VArray_Make(&game->entities, Dqn_ZeroMem_No); Dqn_FArray_Add(&game->parent_entity_stack, game->root_entity->handle); @@ -478,12 +540,14 @@ void TELY_DLL_Init(void *user_data) // NOTE: Hero { + TELY_AssetSpriteSheet *sheet = &game->protag_walk_sprite_sheet; + Dqn_Slice anims = game->protag_walk_sprite_anims; + FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Terry"); entity->local_pos = Dqn_V2_InitNx2(1334, 396); - entity->size_scale = Dqn_V2_InitNx1(4); - entity->sprite_sheet = &game->hero_sprite_sheet; - entity->sprite_anims = game->hero_sprite_anims; - entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); + entity->sprite_sheet = sheet; + entity->sprite_anims = anims; + entity->local_hit_box_size = Dqn_V2_InitV2I(sheet->sprite_size); entity->flags |= FP_GameEntityFlag_Clickable; entity->flags |= FP_GameEntityFlag_MoveByKeyboard; entity->flags |= FP_GameEntityFlag_MoveByMouse; @@ -540,7 +604,7 @@ void TELY_DLL_Init(void *user_data) } // NOTE: Mob spawner - { + if (0) { FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob spawner"); entity->local_pos = Dqn_V2_InitNx2(0, platform->core.window_size.y * .5f); entity->flags |= FP_GameEntityFlag_Clickable; @@ -775,7 +839,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, if (action->state == FP_GameEntityState_Idle) { if (action->flags & FP_GameEntityActionFlag_StateTransition) { - TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Idle")).index; + TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Walk idle")).index; FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, anim); } else if (we_are_clicked_entity) { if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) { @@ -831,7 +895,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, if (action->state == FP_GameEntityState_Run) { if (action->flags & FP_GameEntityActionFlag_StateTransition) { - TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Run")).index; + TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Walk right")).index; FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, anim); } else if (we_are_clicked_entity) { if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) { @@ -995,7 +1059,6 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) // NOTE: Render entity sprites ============================================================= if (entity->sprite_sheet && entity->action.anim) { - TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet; FP_GameEntityAction const *action = &entity->action; TELY_AssetSpriteAnimation const *sprite_anim = action->anim; @@ -1006,10 +1069,19 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row; Dqn_Rect src_rect = {}; - src_rect.pos.x = DQN_CAST(Dqn_f32)(sprite_sheet_column * sprite_sheet->sprite_size.w); - src_rect.pos.y = DQN_CAST(Dqn_f32)(sprite_sheet_row * sprite_sheet->sprite_size.y); - src_rect.size.w = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.w; - src_rect.size.h = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.h; + switch (sprite_sheet->type) { + case TELY_AssetSpriteSheetType_Uniform: { + src_rect.pos.x = DQN_CAST(Dqn_f32)(sprite_sheet_column * sprite_sheet->sprite_size.w); + src_rect.pos.y = DQN_CAST(Dqn_f32)(sprite_sheet_row * sprite_sheet->sprite_size.y); + src_rect.size.w = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.w; + src_rect.size.h = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.h; + } break; + + case TELY_AssetSpriteSheetType_Rects: { + DQN_ASSERT(sprite_index < sprite_sheet->rects.size); + src_rect = sprite_sheet->rects.data[sprite_index]; + } break; + } Dqn_Rect dest_rect = {}; dest_rect.size = src_rect.size * entity->size_scale; @@ -1122,7 +1194,7 @@ void TELY_DLL_FrameUpdate(void *user_data) } Dqn_f32 const PHYSICS_STEP = 1 / 60.f; - for (game->delta_s_accumulator += input->delta_s; + for (game->delta_s_accumulator += DQN_CAST(Dqn_f32)input->delta_s; game->delta_s_accumulator > PHYSICS_STEP; game->delta_s_accumulator -= PHYSICS_STEP) { FP_Update(platform, game, renderer, input); diff --git a/feely_pona_game.h b/feely_pona_game.h index e27d48e..13c1118 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -148,6 +148,9 @@ struct FP_Game Dqn_FArray parent_entity_stack; Dqn_VArray entities; + TELY_AssetSpriteSheet protag_walk_sprite_sheet; + Dqn_Slice protag_walk_sprite_anims; + FP_GameEntity *root_entity; FP_GameEntity *entity_free_list; diff --git a/feely_pona_sprite_packer.cpp b/feely_pona_sprite_packer.cpp new file mode 100644 index 0000000..d651227 --- /dev/null +++ b/feely_pona_sprite_packer.cpp @@ -0,0 +1,120 @@ +#define DQN_ASAN_POISON 1 +#define DQN_ASAN_VET_POISON 1 +#define DQN_ONLY_RECT +#define DQN_ONLY_WIN +#define DQN_ONLY_V2 +#define DQN_ONLY_SLICE +#define DQN_ONLY_SARRAY +#define DQN_ONLY_LIST +#define DQN_ONLY_FS +#define _CRT_SECURE_NO_WARNINGS +#define DQN_IMPLEMENTATION +#include "External/tely/External/dqn/dqn.h" + +DQN_MSVC_WARNING_PUSH +DQN_MSVC_WARNING_DISABLE(4244) // warning C4244: 'argument': conversion from 'int' to 'short', possible loss of data +#define STB_RECT_PACK_IMPLEMENTATION +#include "External/tely/external/stb/stb_rect_pack.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "External/tely/external/stb/stb_image_write.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "External/tely/external/stb/stb_image.h" +DQN_MSVC_WARNING_POP + +int main(int argc, char const *argv[]) +{ + if (argc != 2) { + Dqn_Log_InfoF("USAGE: feely_pona_sprite_packer \"\""); + return -1; + } + + Dqn_Library_Init(); + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8 dir = Dqn_String8_InitCString8(argv[1]); + + // NOTE: Get the list of files ================================================================= + Dqn_List file_list = 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); + *item = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s", DQN_STRING_FMT(dir), DQN_STRING_FMT(it.file_name)); + } + } + + // NOTE: Setup the rect-pack state ============================================================= + int atlas_size = 2048; + stbrp_node *nodes = Dqn_Arena_NewArray(scratch.arena, stbrp_node, file_list.count, Dqn_ZeroMem_Yes); + stbrp_context pack_context = {}; + stbrp_init_target(&pack_context, atlas_size, atlas_size, nodes, DQN_CAST(int)file_list.count); + + // 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);) { + 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*/); + DQN_ASSERT(pixels); + stbi_image_free(pixels); + + // NOTE: Add a rect to the packing context + stbrp_rect *rect = Dqn_SArray_Make(&rects, Dqn_ZeroMem_Yes); + rect->w = x; + rect->h = y; + + Dqn_Log_InfoF("Packing sprite: %.*s", DQN_STRING_FMT(*it.data)); + } + + // NOTE: Pack the rects ======================================================================== + if (stbrp_pack_rects(&pack_context, rects.data, DQN_CAST(int)rects.size) != 1) { + Dqn_Log_ErrorF("STB rect pack failed to pack font rects into rectangle [width=%d, height=%d, num_rects=%d]", + atlas_size, + atlas_size, + file_list.count); + return -1; + } + + // NOTE: Load the files once more and generate the final image ================================= + int final_bpp = 4; + int final_image_stride = atlas_size * final_bpp; + char *final_image = Dqn_Arena_NewArray(scratch.arena, char, atlas_size * final_image_stride, Dqn_ZeroMem_Yes); + + // NOTE: Generate the meta file ================================================================ + Dqn_String8 meta_path = Dqn_String8_InitF(scratch.allocator, "%.*s.txt", DQN_STRING_FMT(dir)); + Dqn_FsFile meta_file = Dqn_Fs_OpenFile(meta_path, Dqn_FsFileOpen_CreateAlways, Dqn_FsFileAccess_Write); + + Dqn_Log_InfoF("Generating meta file: %.*s", DQN_STRING_FMT(meta_path)); + for (Dqn_ListIterator it = {}; Dqn_List_Iterate(&file_list, &it, 0);) { + int w, h, channels_in_file; + stbi_uc *loaded_image = stbi_load(it.data->data, &w, &h, &channels_in_file, 4 /*desired_channels*/); + + stbrp_rect packed_rect = rects.data[it.index]; + char *src = DQN_CAST(char *)loaded_image; + + // NOTE: Generate the image ================================================================ + for (int y = 0; y < packed_rect.h; y++) { + char *row = final_image + ((packed_rect.y + y) * final_image_stride) + (packed_rect.x * final_bpp); + for (int x = 0; x < packed_rect.w; x++) { + *row++ = *src++; + *row++ = *src++; + *row++ = *src++; + *row++ = *src++; + } + } + + stbi_image_free(loaded_image); + + // NOTE: Write the sprite and the rects to the sheet + Dqn_String8 file_name = Dqn_String8_FileNameFromPath(*it.data); + Dqn_String8 file_name_without_extension = Dqn_String8_BinarySplit(file_name, DQN_STRING8(".")).lhs; + + Dqn_Fs_WriteFileF(&meta_file, "%.*s;%d;%d;%d;%d", DQN_STRING_FMT(file_name_without_extension), packed_rect.x, packed_rect.y, packed_rect.w, packed_rect.h); + if (it.index != (file_list.count - 1)) + Dqn_Fs_WriteFileF(&meta_file, "\n"); + } + + Dqn_String8 atlas_path = Dqn_String8_InitF(scratch.allocator, "%.*s.png", DQN_STRING_FMT(dir)); + Dqn_Log_InfoF("Generating atlas: %.*s", DQN_STRING_FMT(atlas_path)); + stbi_write_png(atlas_path.data, atlas_size, atlas_size, 4, final_image, final_image_stride); + return 0; +} diff --git a/project.rdbg b/project.rdbg index 3fe8922a366041ad7500b34eff662f32536a3df0..42beef25cf6f120849dc21287868ff5d5a20168c 100644 GIT binary patch literal 1151 zcmcIkOG^VW5N;KGp`aJ>;9*aqXct6JMLcvXdJ)8mr$9(|ry9&=*Cef}zuuYMzN~cZ zrGZIiHZ$Ls$CrI>x9%@%wOZ6<4_OK<$`W8b05#-!rUh&DrAlT#@YD|C5IkT_(KfJe zp)CTq^A3y_iaEq&FpjM2GBj==c03pHNBABsjWWSZJ~cz*xe~6InKX>^{JH{Wx-o8Z zBOMgfV5si4qHaQ1Y=XK8B{yQIWS0=mD-dRw-$V+OezH_LWnyA&A8idyx^3*^OevXw zu@q=LRqmvMEeukrr@ruoe>9sE`rsxQXN7)#?lwqARiWP@yivqGQ6*qYs$C3-lE+zJ zJMQP=;qh!TmzL`c*JZ>2*DSgUNH#`s2Y#K9pg`{tppw=d(kv>&hKX?EA}D<;zbCT8 zgz`L^I4;uOAMyUabX0Mb=GsWjUhr)D1}qcF05rL#Vfy;2VZSnyG)h2QtY+M8j-7u- ty3tiOQcJx~npic{{4R+30Id*xK>v~8^s)0Ao$cYrPVw-9Cw%i@egGR8A_)Kh delta 57 zcmey*@rH4N