diff --git a/Data/Textures/sprite_spec.txt b/Data/Textures/sprite_spec.txt new file mode 100644 index 0000000..2c232c1 --- /dev/null +++ b/Data/Textures/sprite_spec.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44bf276c7b3964cb26b814255af5b025f7ae4eb9578fb8abe615f629aa98f46b +size 64 diff --git a/Data/Textures/terry_movement.txt b/Data/Textures/terry_movement.txt index 8e7424a..bb487a8 100644 --- a/Data/Textures/terry_movement.txt +++ b/Data/Textures/terry_movement.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b56676681ae0b23c31ebd3d1671ad40319826232317295d1dee7668295cc5a98 -size 781 +oid sha256:786c4f514baf2c413109503bf02ab442695a9bf2e71edc8fbc5f53150e81dbb0 +size 670 diff --git a/Data/Textures/terry_movement/Down_1.png b/Data/Textures/terry_movement/Walk_down_1.png similarity index 100% rename from Data/Textures/terry_movement/Down_1.png rename to Data/Textures/terry_movement/Walk_down_1.png diff --git a/Data/Textures/terry_movement/Down_2.png b/Data/Textures/terry_movement/Walk_down_2.png similarity index 100% rename from Data/Textures/terry_movement/Down_2.png rename to Data/Textures/terry_movement/Walk_down_2.png diff --git a/Data/Textures/terry_movement/Down_3.png b/Data/Textures/terry_movement/Walk_down_3.png similarity index 100% rename from Data/Textures/terry_movement/Down_3.png rename to Data/Textures/terry_movement/Walk_down_3.png diff --git a/Data/Textures/terry_movement/Down_4.png b/Data/Textures/terry_movement/Walk_down_4.png similarity index 100% rename from Data/Textures/terry_movement/Down_4.png rename to Data/Textures/terry_movement/Walk_down_4.png diff --git a/Data/Textures/terry_movement/Down_5.png b/Data/Textures/terry_movement/Walk_down_5.png similarity index 100% rename from Data/Textures/terry_movement/Down_5.png rename to Data/Textures/terry_movement/Walk_down_5.png diff --git a/Data/Textures/terry_movement/Down_6.png b/Data/Textures/terry_movement/Walk_down_6.png similarity index 100% rename from Data/Textures/terry_movement/Down_6.png rename to Data/Textures/terry_movement/Walk_down_6.png diff --git a/Data/Textures/terry_movement/Down_7.png b/Data/Textures/terry_movement/Walk_down_7.png similarity index 100% rename from Data/Textures/terry_movement/Down_7.png rename to Data/Textures/terry_movement/Walk_down_7.png diff --git a/Data/Textures/terry_movement/Idle_1.png b/Data/Textures/terry_movement/Walk_idle_1.png similarity index 100% rename from Data/Textures/terry_movement/Idle_1.png rename to Data/Textures/terry_movement/Walk_idle_1.png diff --git a/Data/Textures/terry_movement/Idle_2.png b/Data/Textures/terry_movement/Walk_idle_2.png similarity index 100% rename from Data/Textures/terry_movement/Idle_2.png rename to Data/Textures/terry_movement/Walk_idle_2.png diff --git a/Data/Textures/terry_movement/Idle_3.png b/Data/Textures/terry_movement/Walk_idle_3.png similarity index 100% rename from Data/Textures/terry_movement/Idle_3.png rename to Data/Textures/terry_movement/Walk_idle_3.png diff --git a/Data/Textures/terry_movement/Idle_4.png b/Data/Textures/terry_movement/Walk_idle_4.png similarity index 100% rename from Data/Textures/terry_movement/Idle_4.png rename to Data/Textures/terry_movement/Walk_idle_4.png diff --git a/Data/Textures/terry_movement/Idle_5.png b/Data/Textures/terry_movement/Walk_idle_5.png similarity index 100% rename from Data/Textures/terry_movement/Idle_5.png rename to Data/Textures/terry_movement/Walk_idle_5.png diff --git a/Data/Textures/terry_movement/Idle_6.png b/Data/Textures/terry_movement/Walk_idle_6.png similarity index 100% rename from Data/Textures/terry_movement/Idle_6.png rename to Data/Textures/terry_movement/Walk_idle_6.png diff --git a/Data/Textures/terry_movement/Idle_7.png b/Data/Textures/terry_movement/Walk_idle_7.png similarity index 100% rename from Data/Textures/terry_movement/Idle_7.png rename to Data/Textures/terry_movement/Walk_idle_7.png diff --git a/Data/Textures/terry_movement/Idle_8.png b/Data/Textures/terry_movement/Walk_idle_8.png similarity index 100% rename from Data/Textures/terry_movement/Idle_8.png rename to Data/Textures/terry_movement/Walk_idle_8.png diff --git a/Data/Textures/terry_movement/Left_1.png b/Data/Textures/terry_movement/Walk_left_1.png similarity index 100% rename from Data/Textures/terry_movement/Left_1.png rename to Data/Textures/terry_movement/Walk_left_1.png diff --git a/Data/Textures/terry_movement/Left_2.png b/Data/Textures/terry_movement/Walk_left_2.png similarity index 100% rename from Data/Textures/terry_movement/Left_2.png rename to Data/Textures/terry_movement/Walk_left_2.png diff --git a/Data/Textures/terry_movement/Left_3.png b/Data/Textures/terry_movement/Walk_left_3.png similarity index 100% rename from Data/Textures/terry_movement/Left_3.png rename to Data/Textures/terry_movement/Walk_left_3.png diff --git a/Data/Textures/terry_movement/Left_4.png b/Data/Textures/terry_movement/Walk_left_4.png similarity index 100% rename from Data/Textures/terry_movement/Left_4.png rename to Data/Textures/terry_movement/Walk_left_4.png diff --git a/Data/Textures/terry_movement/Left_5.png b/Data/Textures/terry_movement/Walk_left_5.png similarity index 100% rename from Data/Textures/terry_movement/Left_5.png rename to Data/Textures/terry_movement/Walk_left_5.png diff --git a/Data/Textures/terry_movement/Left_6.png b/Data/Textures/terry_movement/Walk_left_6.png similarity index 100% rename from Data/Textures/terry_movement/Left_6.png rename to Data/Textures/terry_movement/Walk_left_6.png diff --git a/Data/Textures/terry_movement/Left_7.png b/Data/Textures/terry_movement/Walk_left_7.png similarity index 100% rename from Data/Textures/terry_movement/Left_7.png rename to Data/Textures/terry_movement/Walk_left_7.png diff --git a/Data/Textures/terry_movement/Right_1.png b/Data/Textures/terry_movement/Walk_right_1.png similarity index 100% rename from Data/Textures/terry_movement/Right_1.png rename to Data/Textures/terry_movement/Walk_right_1.png diff --git a/Data/Textures/terry_movement/Right_2.png b/Data/Textures/terry_movement/Walk_right_2.png similarity index 100% rename from Data/Textures/terry_movement/Right_2.png rename to Data/Textures/terry_movement/Walk_right_2.png diff --git a/Data/Textures/terry_movement/Right_3.png b/Data/Textures/terry_movement/Walk_right_3.png similarity index 100% rename from Data/Textures/terry_movement/Right_3.png rename to Data/Textures/terry_movement/Walk_right_3.png diff --git a/Data/Textures/terry_movement/Right_4.png b/Data/Textures/terry_movement/Walk_right_4.png similarity index 100% rename from Data/Textures/terry_movement/Right_4.png rename to Data/Textures/terry_movement/Walk_right_4.png diff --git a/Data/Textures/terry_movement/Right_5.png b/Data/Textures/terry_movement/Walk_right_5.png similarity index 100% rename from Data/Textures/terry_movement/Right_5.png rename to Data/Textures/terry_movement/Walk_right_5.png diff --git a/Data/Textures/terry_movement/Right_6.png b/Data/Textures/terry_movement/Walk_right_6.png similarity index 100% rename from Data/Textures/terry_movement/Right_6.png rename to Data/Textures/terry_movement/Walk_right_6.png diff --git a/Data/Textures/terry_movement/Right_7.png b/Data/Textures/terry_movement/Walk_right_7.png similarity index 100% rename from Data/Textures/terry_movement/Right_7.png rename to Data/Textures/terry_movement/Walk_right_7.png diff --git a/Data/Textures/terry_movement/Up_1.png b/Data/Textures/terry_movement/Walk_up_1.png similarity index 100% rename from Data/Textures/terry_movement/Up_1.png rename to Data/Textures/terry_movement/Walk_up_1.png diff --git a/Data/Textures/terry_movement/Up_2.png b/Data/Textures/terry_movement/Walk_up_2.png similarity index 100% rename from Data/Textures/terry_movement/Up_2.png rename to Data/Textures/terry_movement/Walk_up_2.png diff --git a/Data/Textures/terry_movement/Up_3.png b/Data/Textures/terry_movement/Walk_up_3.png similarity index 100% rename from Data/Textures/terry_movement/Up_3.png rename to Data/Textures/terry_movement/Walk_up_3.png diff --git a/Data/Textures/terry_movement/Up_4.png b/Data/Textures/terry_movement/Walk_up_4.png similarity index 100% rename from Data/Textures/terry_movement/Up_4.png rename to Data/Textures/terry_movement/Walk_up_4.png diff --git a/Data/Textures/terry_movement/Up_5.png b/Data/Textures/terry_movement/Walk_up_5.png similarity index 100% rename from Data/Textures/terry_movement/Up_5.png rename to Data/Textures/terry_movement/Walk_up_5.png diff --git a/Data/Textures/terry_movement/Up_6.png b/Data/Textures/terry_movement/Walk_up_6.png similarity index 100% rename from Data/Textures/terry_movement/Up_6.png rename to Data/Textures/terry_movement/Walk_up_6.png diff --git a/External/tely b/External/tely index a3816ec..f7b7d4f 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit a3816ec63e2a930c067d2598e6c2f96815a4294c +Subproject commit f7b7d4f505c10ebd849793d9c3cf9c89fe696942 diff --git a/feely_pona.cpp b/feely_pona.cpp index c162823..07c01da 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -402,63 +402,83 @@ void TELY_DLL_Init(void *user_data) 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/terry_movement.png", DQN_STRING_FMT(assets->textures_dir)); - sheet->tex_handle = platform->func_load_texture(assets, DQN_STRING8("Terry Movement"), 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; - }; + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + sheet->sprite_size = Dqn_V2I_InitNx2(185, 170); + sheet->type = TELY_AssetSpriteSheetType_Rects; // 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/terry_movement.txt", DQN_STRING_FMT(assets->textures_dir)); - Dqn_String8 sheet_meta_file = Dqn_Fs_Read(sheet_meta_path, scratch.allocator); + Dqn_String8 sprite_spec_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/terry_movement.txt", DQN_STRING_FMT(assets->textures_dir)); + Dqn_String8 sprite_spec_buffer = platform->func_load_file(scratch.arena, sprite_spec_path); + Dqn_String8SplitAllocResult lines = Dqn_String8_SplitAlloc(scratch.allocator, sprite_spec_buffer, DQN_STRING8("\n")); + Dqn_usize sprite_rect_index = 0; + Dqn_usize sprite_anim_index = 0; - 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(";")); + DQN_FOR_UINDEX(line_index, lines.size) { + Dqn_String8 line = lines.data[line_index]; + Dqn_String8SplitAllocResult line_splits = Dqn_String8_SplitAlloc(scratch.allocator, line, 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; + if (line_index == 0) { + DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for @file lines"); + DQN_ASSERT(Dqn_String8_StartsWith(line_splits.data[0], DQN_STRING8("@file"), Dqn_String8EqCase_Sensitive)); - first = split.rhs; - } while (first.size); + // NOTE: Sprite sheet path + Dqn_String8 sprite_sheet_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s", DQN_STRING_FMT(assets->textures_dir), DQN_STRING_FMT(line_splits.data[1])); + sheet->tex_handle = platform->func_load_texture(assets, DQN_STRING8("Terry Movement"), sprite_sheet_path); + DQN_ASSERTF(Dqn_Fs_Exists(sprite_sheet_path), "Required file does not exist '%.*s'", DQN_STRING_FMT(sprite_sheet_path)); - // 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; + // NOTE: Total sprite frame count + Dqn_String8ToU64Result total_frame_count = Dqn_String8_ToU64(line_splits.data[2], 0); + DQN_ASSERT(total_frame_count.success); + sheet->rects = Dqn_Slice_Alloc(&platform->arena, total_frame_count.value, Dqn_ZeroMem_No); + + // NOTE: Total animation count + Dqn_String8ToU64Result total_anim_count = Dqn_String8_ToU64(line_splits.data[3], 0); + DQN_ASSERT(total_anim_count.success); + *anims = Dqn_Slice_Alloc(&platform->arena, total_anim_count.value, Dqn_ZeroMem_No); + + // TODO: Sprite size? + + // TODO: Texture name? + continue; + } + + if (Dqn_String8_StartsWith(line, DQN_STRING8("@anim"))) { + DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for @anim lines"); + Dqn_String8 anim_name = line_splits.data[1]; + Dqn_String8ToU64Result frames_per_second = Dqn_String8_ToU64(line_splits.data[2], 0); + Dqn_String8ToU64Result frame_count = Dqn_String8_ToU64(line_splits.data[3], 0); + + DQN_ASSERT(anim_name.size); + DQN_ASSERT(frame_count.success); + DQN_ASSERT(frames_per_second.success); + + TELY_AssetSpriteAnimation *anim = anims->data + sprite_anim_index++; + anim->label = Dqn_String8_Copy(platform->allocator, anim_name); + anim->index = DQN_CAST(uint16_t)sprite_rect_index; + anim->count = DQN_CAST(uint16_t)frame_count.value; + anim->seconds_per_frame = 1.f / frames_per_second.value; + } else { + DQN_ASSERTF(line_splits.size == 4, "Expected 4 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); + Dqn_String8ToU64Result h = Dqn_String8_ToU64(line_splits.data[3], 0); + + DQN_ASSERT(x.success); + DQN_ASSERT(y.success); + DQN_ASSERT(w.success); + DQN_ASSERT(h.success); + + sheet->rects.data[sprite_rect_index++] = + Dqn_Rect_InitNx4(DQN_CAST(Dqn_f32) x.value, + DQN_CAST(Dqn_f32) y.value, + DQN_CAST(Dqn_f32) w.value, + DQN_CAST(Dqn_f32) h.value); } } - TELY_AssetSpriteAnimation raw_anims[] = { - {DQN_STRING8("Walk down"), /*index*/ 0, /*count*/ 7, /*seconds_per_frame*/ 1 / 8.f}, - {DQN_STRING8("Walk idle"), /*index*/ 7, /*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}, - {DQN_STRING8("Walk up"), /*index*/ 29, /*count*/ 6, /*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)); + DQN_ASSERT(sheet->rects.size == sprite_rect_index); + DQN_ASSERT(anims->size == sprite_anim_index); } game->entities = Dqn_VArray_Init(&platform->arena, 1024 * 8); @@ -851,7 +871,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("Walk 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)) { @@ -907,7 +927,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("Walk right")).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)) { @@ -1076,17 +1096,16 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) TELY_AssetSpriteAnimation const *sprite_anim = action->anim; uint16_t anim_frame = DQN_CAST(uint16_t)(action->timer_s / sprite_anim->seconds_per_frame) % sprite_anim->count; - Dqn_usize sprite_index = sprite_anim->index + anim_frame; - Dqn_usize sprite_sheet_row = sprite_index / sprite_sheet->sprites_per_row; - Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row; - - Dqn_Rect src_rect = {}; + Dqn_usize sprite_index = sprite_anim->index + anim_frame; + Dqn_Rect src_rect = {}; 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; + Dqn_usize sprite_sheet_row = sprite_index / sprite_sheet->sprites_per_row; + Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row; + 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: { diff --git a/feely_pona_sprite_packer.cpp b/feely_pona_sprite_packer.cpp index d651227..a0cfa07 100644 --- a/feely_pona_sprite_packer.cpp +++ b/feely_pona_sprite_packer.cpp @@ -5,6 +5,7 @@ #define DQN_ONLY_V2 #define DQN_ONLY_SLICE #define DQN_ONLY_SARRAY +#define DQN_ONLY_DSMAP #define DQN_ONLY_LIST #define DQN_ONLY_FS #define _CRT_SECURE_NO_WARNINGS @@ -23,16 +24,52 @@ DQN_MSVC_WARNING_DISABLE(4244) // warning C4244: 'argument': conversion from 'in #include "External/tely/external/stb/stb_image.h" DQN_MSVC_WARNING_POP +struct SpriteSpecification +{ + uint16_t frame_count; + uint16_t frames_per_second; +}; + int main(int argc, char const *argv[]) { - if (argc != 2) { - Dqn_Log_InfoF("USAGE: feely_pona_sprite_packer \"\""); + if (argc != 3) { + 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]); + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8 sprite_spec_path = Dqn_String8_InitCString8(argv[1]); + Dqn_String8 dir = Dqn_String8_InitCString8(argv[2]); + + // NOTE: Parse the sprite specifications ======================================================= + if (!Dqn_Fs_Exists(sprite_spec_path)) { + Dqn_Log_ErrorF("Sprite specification file does not exist, we tried to find \"%.*s\" but it does not exist", DQN_STRING_FMT(sprite_spec_path)); + return -1; + } + + Dqn_DSMap sprite_spec_table = Dqn_DSMap_Init(1024); + { + Dqn_ThreadScratch inner_scratch = Dqn_Thread_GetScratch(scratch.arena); + Dqn_String8 sprite_spec_buffer = Dqn_Fs_Read(sprite_spec_path, inner_scratch.allocator); + Dqn_String8SplitAllocResult sprite_spec_lines = Dqn_String8_SplitAlloc(inner_scratch.allocator, sprite_spec_buffer, DQN_STRING8("\n")); + DQN_FOR_UINDEX(line_index, sprite_spec_lines.size) { + Dqn_String8 line = sprite_spec_lines.data[line_index]; + Dqn_String8SplitAllocResult line_parts = Dqn_String8_SplitAlloc(inner_scratch.allocator, line, DQN_STRING8(";")); + DQN_ASSERTF(line_parts.size == 2, "Line must have 2 parts in the sprite specification\n" + "\n" + ";\\n\n" + "\n" + "Line was '%.*s' loaded from '%.*s'", DQN_STRING_FMT(line), sprite_spec_path); + + Dqn_String8 anim_name = line_parts.data[0]; + Dqn_String8ToU64Result frames_per_second = Dqn_String8_ToU64(line_parts.data[1], 0); + DQN_ASSERTF(frames_per_second.success, "Frames per second was not a convertible number, line was '%.*s'", line); + + Dqn_DSMapResult slot = Dqn_DSMap_MakeKeyString8Copy(&sprite_spec_table, scratch.allocator, anim_name); + slot.value->frames_per_second = DQN_CAST(uint16_t)frames_per_second.value; + } + } // NOTE: Get the list of files ================================================================= Dqn_List file_list = Dqn_List_Init(scratch.arena, 128); @@ -49,7 +86,7 @@ int main(int argc, char const *argv[]) 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 + // 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; @@ -57,11 +94,32 @@ int main(int argc, char const *argv[]) DQN_ASSERT(pixels); stbi_image_free(pixels); - // NOTE: Add a rect to the packing context + // NOTE: Add a rect to the packing context ================================================= stbrp_rect *rect = Dqn_SArray_Make(&rects, Dqn_ZeroMem_Yes); rect->w = x; rect->h = y; + // NOTE: Enumerate the number of frames for this animation ================================= + Dqn_String8 file_name = Dqn_String8_FileNameFromPath(*it.data); + Dqn_String8 file_name_without_extension = Dqn_String8_BinarySplit(file_name, DQN_STRING8(".")).lhs; + Dqn_String8BinarySplitResult split = Dqn_String8_BinarySplitReverse(file_name_without_extension, DQN_STRING8("_")); + Dqn_String8 anim_prefix = split.lhs; + + Dqn_DSMapResult slot = Dqn_DSMap_FindKeyString8(&sprite_spec_table, anim_prefix); + DQN_ASSERTF(slot.found, + "\n\nSprite frame loaded from file\n" + " '%.*s'\n" + "\n" + "however there's no corresponding entry in the specification file loaded at\n" + " '%.*s'\n" + "\n" + "Add a line in format of ; to the file, e.g.\n" + " %.*s;8\n", + DQN_STRING_FMT(*it.data), + 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)); } @@ -78,12 +136,19 @@ int main(int argc, char const *argv[]) 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); + Dqn_String8 atlas_path = Dqn_String8_InitF(scratch.allocator, "%.*s.png", DQN_STRING_FMT(dir)); // 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_Fs_WriteFileF(&meta_file, + "@file;%.*s;%d;%d\n", + DQN_STRING_FMT(Dqn_String8_FileNameFromPath(atlas_path)), + DQN_CAST(int) file_list.count, + DQN_CAST(int) sprite_spec_table.occupied - 1); Dqn_Log_InfoF("Generating meta file: %.*s", DQN_STRING_FMT(meta_path)); + Dqn_String8 anim_prefix = {}; 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*/); @@ -105,15 +170,25 @@ int main(int argc, char const *argv[]) 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 = 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); + // NOTE: Detect what animation we are currently processing ================================= + if (anim_prefix.size == 0 || !Dqn_String8_StartsWith(file_name_without_extension, anim_prefix)) { + // NOTE: Anim prefix is different, we are starting a new animation- mark it accordingly + Dqn_String8BinarySplitResult split = Dqn_String8_BinarySplitReverse(file_name_without_extension, DQN_STRING8("_")); + anim_prefix = split.lhs; + + Dqn_DSMapResult slot = Dqn_DSMap_FindKeyString8(&sprite_spec_table, anim_prefix); + Dqn_Fs_WriteFileF(&meta_file, "@anim;%.*s;%u;%u\n", DQN_STRING_FMT(anim_prefix), slot.value->frames_per_second, slot.value->frame_count); + } + + // 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_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 42beef2..81ea7a9 100644 Binary files a/project.rdbg and b/project.rdbg differ