commit 59aaf5d09363c2530a106c1055f355b291ce5d03 Author: doyle Date: Sat Sep 16 12:21:24 2023 +1000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..527b85c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Build/ diff --git a/External/tely b/External/tely new file mode 160000 index 0000000..b59630f --- /dev/null +++ b/External/tely @@ -0,0 +1 @@ +Subproject commit b59630fb8f3ecaa4be1382d9bdcc236afea3ce93 diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..a1ed45d --- /dev/null +++ b/build.bat @@ -0,0 +1,107 @@ +@echo off +setlocal + +set script_dir_backslash=%~dp0 +set script_dir=%script_dir_backslash:~0,-1% +set build_dir=%script_dir%\Build +set code_dir=%script_dir% +set tely_dir=%code_dir%\External\tely + +REM ================================================================================================ + +mkdir %build_dir% 2>nul +pushd %build_dir% +mkdir %build_dir%\Data 2>nul + +REM ================================================================================================ + +set robocopy_cmd=robocopy /MIR /NJH /NJS /NDL /NP %code_dir%\Data %build_dir%\Data +call powershell -Command "$duration = Measure-Command {%robocopy_cmd% | Out-Default}; Write-Host 'robocopy:' $duration.TotalSeconds 'seconds'" + +REM ================================================================================================ + +REM TODO: Raylib seems to have problems shutting down fsanitize=address? +set common_compile_flags=/W4 /Z7 /MT /EHsc /nologo /I %tely_dir% +set common_link_flags=/link /incremental:no + +REM raylib ========================================================================================= + +set raylib_dir=%tely_dir%\external\raylib +if not exist %build_dir%\rcore.obj ( + cl %common_compile_flags% ^ + /w ^ + /c ^ + /D _DEFAULT_SOURCE ^ + /D PLATFORM_DESKTOP ^ + /I %raylib_dir% ^ + /I %raylib_dir%\external\glfw\include ^ + /I %raylib_dir%\glfw\deps\mingw ^ + %raylib_dir%\rcore.c ^ + %raylib_dir%\utils.c ^ + %raylib_dir%\raudio.c ^ + %raylib_dir%\rmodels.c ^ + %raylib_dir%\rtext.c ^ + %raylib_dir%\rtextures.c ^ + %raylib_dir%\rshapes.c ^ + %raylib_dir%\rglfw.c +) + +REM raylib flags ========================================================================================= + +set raylib_compile_flags=%common_compile_flags% ^ +/Tp %tely_dir%\tely_platform_raylib_unity.h ^ +/I "%raylib_dir%" + +set raylib_link_flags=%common_link_flags% ^ +%build_dir%\rcore.obj ^ +%build_dir%\utils.obj ^ +%build_dir%\raudio.obj ^ +%build_dir%\rmodels.obj ^ +%build_dir%\rtext.obj ^ +%build_dir%\rtextures.obj ^ +%build_dir%\rshapes.obj ^ +%build_dir%\rglfw.obj ^ +gdi32.lib opengl32.lib winmm.lib user32.lib shell32.lib + +REM DLL flags ====================================================================================== + +set dll_compile_flags=%common_compile_flags% /LD %code_dir%\feely_pona_unity.cpp +set dll_link_flags=%common_link_flags% + +REM MSVC commands ================================================================================== + +set msvc_exe_name=feely_pona_msvc +set msvc_cmd=cl %raylib_compile_flags% /Fo%msvc_exe_name% /Fe%msvc_exe_name% %raylib_link_flags% +set msvc_dll_cmd=cl %dll_compile_flags% /Fotely_dll_msvc /Fetely_dll_msvc %dll_link_flags% + +REM CLANG commands ================================================================================= + +set clang_exe_name_sdl=feely_pona_clang +set clang_cmd_sdl=clang-cl %compile_flags% /Fo%clang_exe_name_sdl% /Fe%clang_exe_name_sdl% %link_flags% +set clang_dll_cmd=clang-cl %dll_compile_flags% /Fotely_dll_clang /Fetely_dll_clang %dll_link_flags% + +REM MSVC build ===================================================================================== + +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' +set msvc_print_platform_and_dll_time=Write-Host 'msvc (platform+dll):'($platform_time.TotalSeconds + $dll_time.TotalSeconds)'s ('$platform_time.TotalSeconds + $dll_time.TotalSeconds')' +set msvc_check_if_exe_locked=$File = [System.IO.File]::Open('%build_dir%\%msvc_exe_name%.exe', 'Open', 'Write'); $File.Close(); $File.Dispose() + +set msvc_exe_is_locked=0 +if exist %build_dir%\%msvc_exe_name%.exe ( + powershell -Command "%msvc_check_if_exe_locked%" 2>nul && set msvc_exe_is_locked=0 || set msvc_exe_is_locked=1 +) + +echo foo> %msvc_exe_name%.lock +if %msvc_exe_is_locked% == 1 ( + powershell -Command "%msvc_build_dll% %msvc_print_dll_time%" +) else ( + powershell -Command "%msvc_build_platform% %msvc_build_dll% %msvc_print_platform_and_dll_time%" +) +del %msvc_exe_name%.lock + +REM CLANG build ==================================================================================== + +popd +exit /B 1 diff --git a/feely_pona.cpp b/feely_pona.cpp new file mode 100644 index 0000000..9c11b04 --- /dev/null +++ b/feely_pona.cpp @@ -0,0 +1,996 @@ +#if defined(__clang__) +#pragma once +#include "feely_pona_unity.h" +#endif + +extern "C" __declspec(dllexport) +void TELY_DLL_Reload(void *user_data) +{ + TELY_Platform *platform = DQN_CAST(TELY_Platform *)user_data; + Dqn_Library_SetPointer(platform->core.dqn_lib); +} + +extern "C" __declspec(dllexport) +void TELY_DLL_Init(void *user_data) +{ + TELY_Platform *platform = DQN_CAST(TELY_Platform *)user_data; + TELY_DLL_Reload(user_data); + + { + Dqn_Arena_TempMemoryScope(&platform->arena); + TELY_ChunkPool pool = {}; + pool.arena = &platform->arena; + + void *bytes16 = TELY_ChunkPool_Alloc(&pool, 16); + TELY_ChunkPool_Dealloc(&pool, bytes16); + DQN_ASSERT(pool.slots[TELY_ChunkPoolSlotSize_16b] == DQN_CAST(void *)(DQN_CAST(char *)bytes16 - sizeof(TELY_ChunkPoolSlot))); + DQN_ASSERT(pool.slots[TELY_ChunkPoolSlotSize_16b]->next == nullptr); + + void *bytes17 = TELY_ChunkPool_Alloc(&pool, 17); + TELY_ChunkPool_Dealloc(&pool, bytes17); + DQN_ASSERT(pool.slots[TELY_ChunkPoolSlotSize_32b] == DQN_CAST(void *)(DQN_CAST(char *)bytes17 - sizeof(TELY_ChunkPoolSlot))); + DQN_ASSERT(pool.slots[TELY_ChunkPoolSlotSize_32b]->next == nullptr); + + void *bytes1 = TELY_ChunkPool_Alloc(&pool, 1); + void *bytes2 = TELY_ChunkPool_Alloc(&pool, 1); + TELY_ChunkPool_Dealloc(&pool, bytes1); + TELY_ChunkPool_Dealloc(&pool, bytes2); + DQN_ASSERT(pool.slots[TELY_ChunkPoolSlotSize_16b] == DQN_CAST(void *)(DQN_CAST(char *)bytes2 - sizeof(TELY_ChunkPoolSlot))); + DQN_ASSERT(pool.slots[TELY_ChunkPoolSlotSize_16b]->next == DQN_CAST(void *)(DQN_CAST(char *)bytes1 - sizeof(TELY_ChunkPoolSlot))); + + void *bytes128k = TELY_ChunkPool_Alloc(&pool, DQN_KILOBYTES(128)); + TELY_ChunkPool_Dealloc(&pool, bytes128k); + DQN_ASSERT(pool.slots[TELY_ChunkPoolSlotSize_128k] == DQN_CAST(void *)(DQN_CAST(char *)bytes128k - sizeof(TELY_ChunkPoolSlot))); + DQN_ASSERT(pool.slots[TELY_ChunkPoolSlotSize_128k]->next == nullptr); + } + + // NOTE: TELY Game ============================================================================= + + TELY_Assets *assets = &platform->assets; + TELY_Game *game = Dqn_Arena_New(&platform->arena, TELY_Game, Dqn_ZeroMem_Yes); + game->chunk_pool.arena = &platform->arena; + platform->user_data = game; + { + TELY_AssetSpriteSheet *sheet = &game->hero_sprite_sheet; + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8 sheet_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/adventurer-v1.5-sheet.png", DQN_STRING_FMT(assets->textures_dir)); + sheet->tex_handle = platform->func_load_texture(assets, DQN_STRING8("Hero"), sheet_path); + sheet->sprite_count = 109; + sheet->sprites_per_row = 7; + sheet->sprite_size = Dqn_V2I_InitNx2(50, 37); + + TELY_AssetSpriteAnimation hero_anims[] = { + {DQN_STRING8("Everything"), /*index*/ 0, /*count*/ sheet->sprite_count}, + {DQN_STRING8("Idle"), /*index*/ 0, /*count*/ 3}, + {DQN_STRING8("Run"), /*index*/ 8, /*count*/ 6}, + {DQN_STRING8("Jump"), /*index*/ 14, /*count*/ 10}, + {DQN_STRING8("Floor slide"), /*index*/ 24, /*count*/ 5}, + {DQN_STRING8("Unknown"), /*index*/ 29, /*count*/ 9}, + {DQN_STRING8("Attack A"), /*index*/ 38, /*count*/ 11}, + {DQN_STRING8("Attack B"), /*index*/ 49, /*count*/ 4}, + {DQN_STRING8("Attack C"), /*index*/ 53, /*count*/ 6}, + {DQN_STRING8("Hurt A"), /*index*/ 59, /*count*/ 5}, + {DQN_STRING8("Hurt B"), /*index*/ 64, /*count*/ 5}, + {DQN_STRING8("Unsheath sword"), /*index*/ 69, /*count*/ 4}, + {DQN_STRING8("Sheath sword"), /*index*/ 73, /*count*/ 4}, + {DQN_STRING8("Air drift"), /*index*/ 77, /*count*/ 2}, + {DQN_STRING8("Air drop"), /*index*/ 79, /*count*/ 2}, + {DQN_STRING8("Ladder climb"), /*index*/ 81, /*count*/ 4}, + {DQN_STRING8("Chi push"), /*index*/ 85, /*count*/ 8}, + {DQN_STRING8("Leap slice A"), /*index*/ 93, /*count*/ 7}, + {DQN_STRING8("Leap slice B"), /*index*/ 100, /*count*/ 3}, + {DQN_STRING8("Leap slice C"), /*index*/ 103, /*count*/ 6}, + }; + + game->hero_sprite_anims = Dqn_Slice_Alloc(&platform->arena, DQN_ARRAY_UCOUNT(hero_anims), Dqn_ZeroMem_No); + DQN_MEMCPY(game->hero_sprite_anims.data, &hero_anims, sizeof(hero_anims[0]) * DQN_ARRAY_UCOUNT(hero_anims)); + } + + game->entities = Dqn_VArray_Init(&platform->arena, 1024 * 8); + game->root_entity = Dqn_VArray_Make(&game->entities, 1, Dqn_ZeroMem_No); + Dqn_FArray_Add(&game->parent_entity_stack, game->root_entity->handle); + + // NOTE: Unit test DFS pre-order and post-order walk + { + // NOTE: Setup entity-tree ================================================================= + + TELY_GameEntity *f = TELY_Game_MakeEntityPointerF(game, "F"); + TELY_Game_PushParentEntity(game, f->handle); + TELY_GameEntity *b = TELY_Game_MakeEntityPointerF(game, "B"); + TELY_GameEntity *g = TELY_Game_MakeEntityPointerF(game, "G"); + TELY_Game_PushParentEntity(game, b->handle); + TELY_GameEntity *a = TELY_Game_MakeEntityPointerF(game, "A"); + TELY_GameEntity *d = TELY_Game_MakeEntityPointerF(game, "D"); + TELY_Game_PushParentEntity(game, d->handle); + TELY_GameEntity *c = TELY_Game_MakeEntityPointerF(game, "C"); + TELY_GameEntity *e = TELY_Game_MakeEntityPointerF(game, "E"); + TELY_Game_PopParentEntity(game); + TELY_Game_PopParentEntity(game); + + TELY_Game_PushParentEntity(game, g->handle); + TELY_GameEntity *i = TELY_Game_MakeEntityPointerF(game, "I"); + TELY_Game_PushParentEntity(game, i->handle); + TELY_GameEntity *h = TELY_Game_MakeEntityPointerF(game, "H"); + TELY_Game_PopParentEntity(game); + TELY_Game_PopParentEntity(game); + TELY_Game_PopParentEntity(game); + + // NOTE: Pre order test ==================================================================== + + TELY_GameEntity *pre_order_walk[9] = {}; + Dqn_usize pre_order_walk_count = 0; + for (TELY_GameEntityIterator it = {}; TELY_Game_DFSPreOrderWalkEntityTree(game, &it, game->root_entity);) { + DQN_ASSERT(pre_order_walk_count < DQN_ARRAY_UCOUNT(pre_order_walk)); + pre_order_walk[pre_order_walk_count++] = it.entity; + } + + DQN_ASSERT(pre_order_walk_count == DQN_ARRAY_UCOUNT(pre_order_walk)); + DQN_ASSERT(pre_order_walk[0] == f); + DQN_ASSERT(pre_order_walk[1] == b); + DQN_ASSERT(pre_order_walk[2] == a); + DQN_ASSERT(pre_order_walk[3] == d); + DQN_ASSERT(pre_order_walk[4] == c); + DQN_ASSERT(pre_order_walk[5] == e); + DQN_ASSERT(pre_order_walk[6] == g); + DQN_ASSERT(pre_order_walk[7] == i); + DQN_ASSERT(pre_order_walk[8] == h); + + // NOTE: Post order test =================================================================== + + TELY_GameEntity *post_order_walk[9] = {}; + Dqn_usize post_order_walk_count = 0; + for (TELY_GameEntityIterator it = {}; TELY_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity);) { + DQN_ASSERT(post_order_walk_count < DQN_ARRAY_UCOUNT(post_order_walk)); + post_order_walk[post_order_walk_count++] = it.entity; + } + + DQN_ASSERT(post_order_walk_count == DQN_ARRAY_UCOUNT(post_order_walk)); + DQN_ASSERT(post_order_walk[0] == a); + DQN_ASSERT(post_order_walk[1] == c); + DQN_ASSERT(post_order_walk[2] == e); + DQN_ASSERT(post_order_walk[3] == d); + DQN_ASSERT(post_order_walk[4] == b); + DQN_ASSERT(post_order_walk[5] == h); + DQN_ASSERT(post_order_walk[6] == i); + DQN_ASSERT(post_order_walk[7] == g); + DQN_ASSERT(post_order_walk[8] == f); + + // NOTE: Cleanup =========================================================================== + TELY_Game_DeleteEntity(game, game->root_entity->handle); + DQN_ASSERT(game->root_entity->first_child == nullptr); + DQN_ASSERT(game->root_entity->last_child == nullptr); + DQN_ASSERT(game->root_entity->next == nullptr); + DQN_ASSERT(game->root_entity->prev == nullptr); + DQN_ASSERT(game->root_entity->parent == nullptr); + } + + // NOTE: Test sprite animation entity + { + TELY_GameEntity *first_entity = TELY_Game_MakeEntityPointerF(game, "Hero"); + first_entity->local_pos = Dqn_V2_InitNx2(100.f, 100.f); + first_entity->size_scale = Dqn_V2_InitNx1(4); + first_entity->sprite_sheet = &game->hero_sprite_sheet; + first_entity->sprite_anims = game->hero_sprite_anims; + first_entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); + first_entity->flags |= TELY_EntityFlag_Clickable; + first_entity->flags |= TELY_EntityFlag_MoveByKeyboard; + first_entity->flags |= TELY_EntityFlag_MoveByMouse; + } + + // NOTE: Freya's Game Dev Math Lecture 01 + TELY_FreyaGameMath *freya_gm = &game->freya_game_math; + { + TELY_GameEntity *group_box = TELY_Game_MakeEntityPointerF(game, "Lecture 01 Group Box"); + TELY_Game_PushParentEntity(game, group_box->handle); + DQN_DEFER { TELY_Game_PopParentEntity(game); }; + { + group_box->local_pos = Dqn_V2_InitNx2(70, 609); + group_box->flags |= TELY_EntityFlag_DrawHitBox; + group_box->flags |= TELY_EntityFlag_DeriveHitBoxFromChildrenBoundingBox; + group_box->flags |= TELY_EntityFlag_Clickable; + group_box->flags |= TELY_EntityFlag_MoveByKeyboard; + group_box->flags |= TELY_EntityFlag_MoveByMouse; + freya_gm->lec01_group_box = group_box->handle; + } + + TELY_GameEntity *coordinate_axis = TELY_Game_MakeEntityPointerF(game, "Coordinate Axis"); + DQN_ASSERT(coordinate_axis->parent == group_box); + { + coordinate_axis->local_hit_box_size = Dqn_V2_InitNx1(24); + coordinate_axis->flags |= TELY_EntityFlag_Clickable; + coordinate_axis->flags |= TELY_EntityFlag_MoveByKeyboard; + coordinate_axis->flags |= TELY_EntityFlag_MoveByMouse; + + Dqn_f32 const axis_length = platform->core.window_size.y * 0.25f; + + TELY_GameShape *axis_y = Dqn_FArray_Make(&coordinate_axis->shapes, Dqn_ZeroMem_Yes); + axis_y->type = TELY_GameShapeType_Line; + axis_y->p2 = Dqn_V2_InitNx2(0, -axis_length); + axis_y->line_thickness = 2.f; + axis_y->colour = TELY_COLOUR_RED_TOMATO_V4; + + TELY_GameShape *axis_x = Dqn_FArray_Make(&coordinate_axis->shapes, Dqn_ZeroMem_Yes); + axis_x->type = TELY_GameShapeType_Line; + axis_x->p2 = Dqn_V2_InitNx2(axis_length, 0); + axis_x->line_thickness = 2.f; + axis_x->colour = TELY_COLOUR_GREEN_DARK_KHAKI_V4; + + freya_gm->lec01_axis = coordinate_axis->handle; + } + + // ========================================================================================= + Dqn_f32 const point_radius = 8.f; + Dqn_f32 const point_diameter = point_radius * 2.f; + + TELY_GameEntity *point_a = TELY_Game_MakeEntityPointerF(game, "A"); + { + point_a->local_pos = Dqn_V2_InitNx2(400, -100); + point_a->local_hit_box_size = Dqn_V2_InitNx1(point_diameter); + point_a->flags |= TELY_EntityFlag_Clickable; + point_a->flags |= TELY_EntityFlag_MoveByKeyboard; + point_a->flags |= TELY_EntityFlag_MoveByMouse; + point_a->parent = coordinate_axis; + + TELY_GameShape *circle = Dqn_FArray_Make(&point_a->shapes, Dqn_ZeroMem_Yes); + circle->type = TELY_GameShapeType_Circle; + circle->circle_radius = point_radius; + circle->colour = TELY_COLOUR_RED_PALE_VIOLET_V4; + freya_gm->lec01_point_a = point_a->handle; + } + + TELY_GameEntity *point_b = TELY_Game_MakeEntityPointerF(game, "B"); + { + point_b->local_pos = Dqn_V2_InitNx2(300, -300); + point_b->local_hit_box_size = Dqn_V2_InitNx1(point_diameter); + point_b->flags |= TELY_EntityFlag_Clickable; + point_b->flags |= TELY_EntityFlag_MoveByKeyboard; + point_b->flags |= TELY_EntityFlag_MoveByMouse; + + TELY_GameShape *circle = Dqn_FArray_Make(&point_b->shapes, Dqn_ZeroMem_Yes); + circle->type = TELY_GameShapeType_Circle; + circle->circle_radius = point_radius; + circle->colour = TELY_COLOUR_BLUE_CADET_V4; + freya_gm->lec01_point_b = point_b->handle; + } + } + + uint16_t font_size = 18; + // NOTE: Freya's Game Dev Math Lecture 01 Assignment 01 + { + TELY_GameEntity *group_box = TELY_Game_MakeEntityPointerF(game, "Assignment 01 Group Box"); + TELY_Game_PushParentEntity(game, group_box->handle); + DQN_DEFER { TELY_Game_PopParentEntity(game); }; + { + group_box->local_pos = Dqn_V2_InitNx2(659, 404); + group_box->flags |= TELY_EntityFlag_DrawHitBox; + group_box->flags |= TELY_EntityFlag_DeriveHitBoxFromChildrenBoundingBox; + group_box->flags |= TELY_EntityFlag_Clickable; + group_box->flags |= TELY_EntityFlag_MoveByKeyboard; + group_box->flags |= TELY_EntityFlag_MoveByMouse; + freya_gm->lec01_task01_group_box = group_box->handle; + } + + // NOTE: Radial trigger + { + TELY_GameEntity *radial_trigger = TELY_Game_MakeEntityPointerF(game, "Radial trigger"); + freya_gm->lec01_task01_radial_trigger = radial_trigger->handle; + freya_gm->lec01_task01_radial_trigger_radius = 100.f; + radial_trigger->local_hit_box_size = Dqn_V2_InitNx1(font_size); + radial_trigger->flags |= TELY_EntityFlag_Clickable; + radial_trigger->flags |= TELY_EntityFlag_MoveByKeyboard; + radial_trigger->flags |= TELY_EntityFlag_MoveByMouse; + + // NOTE: Draw the "sensor" + TELY_GameShape *circle = Dqn_FArray_Make(&radial_trigger->shapes, Dqn_ZeroMem_Yes); + circle->type = TELY_GameShapeType_Circle; + circle->circle_radius = radial_trigger->local_hit_box_size.w * .5f; + circle->colour = TELY_COLOUR_BLUE_CADET_V4; + + // NOTE: Draw the trigger range + TELY_GameShape *trigger_circle = Dqn_FArray_Make(&radial_trigger->shapes, Dqn_ZeroMem_Yes); + trigger_circle->type = TELY_GameShapeType_Circle; + trigger_circle->circle_radius = freya_gm->lec01_task01_radial_trigger_radius; + trigger_circle->colour = TELY_COLOUR_BLUE_CADET_V4; + } + + // NOTE: Movable dot that will trigger the radial trigger + { + TELY_GameEntity *player = TELY_Game_MakeEntityPointerF(game, "Player"); + freya_gm->lec01_task01_player = player->handle; + + player->local_pos = Dqn_V2_InitNx2(118, 0); + player->local_hit_box_size = Dqn_V2_InitNx1(font_size * 2.f); + player->flags |= TELY_EntityFlag_Clickable; + player->flags |= TELY_EntityFlag_MoveByKeyboard; + player->flags |= TELY_EntityFlag_MoveByMouse; + + // NOTE: Draw the player + TELY_GameShape *rect = Dqn_FArray_Make(&player->shapes, Dqn_ZeroMem_Yes); + rect->type = TELY_GameShapeType_Rect; + rect->p2 = player->local_hit_box_size; + rect->colour = TELY_COLOUR_GREEN_DARK_SEA_V4; + } + } + + { + TELY_GameEntity *group_box = TELY_Game_MakeEntityPointerF(game, "Assignment 02 Group Box"); + TELY_Game_PushParentEntity(game, group_box->handle); + DQN_DEFER { TELY_Game_PopParentEntity(game); }; + { + group_box->local_pos = Dqn_V2_InitNx2(761, 590); + group_box->flags |= TELY_EntityFlag_DrawHitBox; + group_box->flags |= TELY_EntityFlag_DeriveHitBoxFromChildrenBoundingBox; + group_box->flags |= TELY_EntityFlag_Clickable; + group_box->flags |= TELY_EntityFlag_MoveByKeyboard; + group_box->flags |= TELY_EntityFlag_MoveByMouse; + freya_gm->lec01_task02_group_box = group_box->handle; + } + + TELY_GameEntity *ray_begin = TELY_Game_MakeEntityPointerF(game, "Ray Begin"); + { + ray_begin->local_pos = Dqn_V2_InitNx2(0, 0); + ray_begin->local_hit_box_size = Dqn_V2_InitNx1(font_size); + ray_begin->flags |= TELY_EntityFlag_Clickable; + ray_begin->flags |= TELY_EntityFlag_MoveByKeyboard; + ray_begin->flags |= TELY_EntityFlag_MoveByMouse; + freya_gm->lec01_task02_ray_begin = ray_begin->handle; + + // NOTE: Draw the ray origin + TELY_GameShape *circle = Dqn_FArray_Make(&ray_begin->shapes, Dqn_ZeroMem_Yes); + circle->type = TELY_GameShapeType_Circle; + circle->circle_radius = ray_begin->local_hit_box_size.w * .5f; + circle->colour = TELY_COLOUR_BLUE_CADET_V4; + } + + TELY_GameEntity *ray_end = TELY_Game_MakeEntityPointerF(game, "Ray End"); + { + ray_end->local_pos = Dqn_V2_InitNx2(100, 220); + ray_end->local_hit_box_size = Dqn_V2_InitNx1(font_size); + ray_end->flags |= TELY_EntityFlag_Clickable; + ray_end->flags |= TELY_EntityFlag_MoveByKeyboard; + ray_end->flags |= TELY_EntityFlag_MoveByMouse; + freya_gm->lec01_task02_ray_end = ray_end->handle; + + // NOTE: Draw the ray origin + TELY_GameShape *circle = Dqn_FArray_Make(&ray_end->shapes, Dqn_ZeroMem_Yes); + circle->type = TELY_GameShapeType_Circle; + circle->circle_radius = ray_end->local_hit_box_size.w * .5f; + circle->colour = TELY_COLOUR_BLUE_CADET_V4; + } + + TELY_GameEntity *surface = TELY_Game_MakeEntityPointerF(game, "Surface"); + { + surface->local_pos = Dqn_V2_InitNx2(0, 200); + surface->local_hit_box_size = Dqn_V2_InitNx2(400, 25); + surface->flags |= TELY_EntityFlag_Clickable; + surface->flags |= TELY_EntityFlag_MoveByKeyboard; + surface->flags |= TELY_EntityFlag_MoveByMouse; + freya_gm->lec01_task02_surface = surface->handle; + + TELY_GameShape *rect = Dqn_FArray_Make(&surface->shapes, Dqn_ZeroMem_Yes); + rect->type = TELY_GameShapeType_Rect; + rect->p2 = surface->local_hit_box_size; + rect->colour = TELY_COLOUR_BLUE_CADET_V4; + } + } + + game->camera.scale = Dqn_V2_InitNx1(1); + game->inter_regular_font = platform->func_load_font(assets, DQN_STRING8("Inter (Regular)"), DQN_STRING8("Data/Inter-Regular.otf"), font_size); + game->inter_italic_font = platform->func_load_font(assets, DQN_STRING8("Inter (Italic)"), DQN_STRING8("Data/Inter-Italic.otf"), font_size); + game->jetbrains_mono_font = platform->func_load_font(assets, DQN_STRING8("JetBrains Mono NL (Regular)"), DQN_STRING8("Data/JetBrainsMonoNL-Regular.ttf"), font_size); + game->test_audio = platform->func_load_audio(assets, DQN_STRING8("Test Audio"), DQN_STRING8("Data/Audio/Purrple Cat - Moonwinds.qoa")); + + // NOTE: TELY audio ============================================================================ + + TELY_Audio *audio = &platform->audio; + audio->chunk_pool = &game->chunk_pool; + + // NOTE: TELY ui =============================================================================== + + TELY_UI *ui = &game->ui; + ui->arena.allocs_are_allowed_to_leak = true; +} + +static void TELY_DLL_FreyaMathForGameDevs(TELY_Platform *platform) +{ + TELY_Assets *assets = &platform->assets; + TELY_Renderer *renderer = &platform->renderer; + TELY_Game *game = DQN_CAST(TELY_Game *) platform->user_data; + TELY_AssetFont *font = TELY_Asset_GetFont(assets, game->jetbrains_mono_font); + Dqn_f32 line_thickness = 2.f; + + TELY_Render_PushFont(renderer, font->handle); + TELY_Render_PushColourV4(renderer, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4); + DQN_DEFER { + TELY_Render_PopFont(renderer); + TELY_Render_PopColourV4(renderer); + }; + + TELY_FreyaGameMath *freya_gm = &game->freya_game_math; + // NOTE: Lecture 01: Numbers, Vectors & Dot Product ============================================ + { + { + // NOTE: Render label onto the group box + TELY_GameEntity const *group_box = TELY_Game_GetEntity(game, freya_gm->lec01_group_box); + Dqn_Rect group_box_world_hit_box = TELY_Game_CalcEntityWorldHitBox(game, group_box->handle); + TELY_Render_Text(renderer, group_box_world_hit_box.pos, Dqn_V2_InitNx2(0.f, 1.f), DQN_STRING8("Numbers, Vectors & Dot Product")); + } + + TELY_GameEntity const *axis = TELY_Game_GetEntity(game, freya_gm->lec01_axis); + TELY_GameEntity const *point_a = TELY_Game_GetEntity(game, freya_gm->lec01_point_a); + TELY_GameEntity const *point_b = TELY_Game_GetEntity(game, freya_gm->lec01_point_b); + + Dqn_V2 const axis_world_pos = TELY_Game_CalcEntityWorldPos(game, axis->handle); + Dqn_V2 const point_a_world_pos = TELY_Game_CalcEntityWorldPos(game, point_a->handle); + Dqn_V2 const point_b_world_pos = TELY_Game_CalcEntityWorldPos(game, point_b->handle); + + // NOTE: Render line from axis -> 'A' + TELY_Render_LineColourV4(renderer, axis_world_pos, point_a_world_pos, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4, line_thickness); + TELY_Render_Text(renderer, point_a_world_pos, Dqn_V2_InitNx1(0.f), DQN_STRING8("A")); + + // NOTE: Render line from axis -> 'B' + TELY_Render_LineColourV4(renderer, axis_world_pos, point_b_world_pos, TELY_COLOUR_BLUE_CADET_V4, line_thickness); + TELY_Render_Text(renderer, point_b_world_pos, Dqn_V2_InitNx1(0.f), DQN_STRING8("B")); + + // NOTE: Normalise point 'A' + Dqn_V2 point_a_norm = Dqn_V2_Normalise(point_a_world_pos - axis_world_pos); + Dqn_V2 point_a_norm_to_axis = axis_world_pos + (point_a_norm * font->pixel_height * 5.f); + + // NOTE: Draw the normalised vector of 'A' + TELY_Render_CircleColourV4(renderer, point_a_norm_to_axis, (point_a->local_hit_box_size.w * .5f), TELY_RenderShapeMode_Fill, TELY_COLOUR_GREEN_DARK_SEA_V4); + TELY_Render_Text(renderer, point_a_norm_to_axis, Dqn_V2_InitNx1(0.5f), DQN_STRING8("A normalised")); + + // NOTE: Draw the scalar projection of 'B' onto 'A' + Dqn_f32 scalar_proj = Dqn_V2_Dot(point_a_norm, point_b_world_pos - axis_world_pos); + TELY_Render_TextF(renderer, + Dqn_V2_InitNx2(axis_world_pos.x, axis_world_pos.y), + Dqn_V2_InitNx1(0.f), + "Scalar Projection: %.2f", scalar_proj); + + // NOTE: Draw the vector projection of 'B' onto 'A' + Dqn_V2 vector_proj = axis_world_pos + (point_a_norm * scalar_proj); + TELY_Render_CircleColourV4(renderer, vector_proj, (point_a->local_hit_box_size.w * .5f), TELY_RenderShapeMode_Fill, TELY_COLOUR_GREEN_DARK_SEA_V4); + TELY_Render_Text(renderer, vector_proj, Dqn_V2_InitNx1(0.5f), DQN_STRING8("Vector Projection")); + + // NOTE: Draw the line projection the vector 'B' onto 'A' + TELY_Render_LineColourV4(renderer, point_b_world_pos, vector_proj, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4, line_thickness); + } + + // NOTE: Lecture 01: Assignment 01 - Radial Trigger ============================================ + { + // NOTE: Render label onto the group box + TELY_GameEntity const *group_box = TELY_Game_GetEntity(game, freya_gm->lec01_task01_group_box); + Dqn_Rect group_box_world_hit_box = TELY_Game_CalcEntityWorldHitBox(game, group_box->handle); + TELY_Render_Text(renderer, group_box_world_hit_box.pos, Dqn_V2_InitNx2(0.f, 1.f), DQN_STRING8("Assignment 01: Radial Trigger")); + + // NOTE: Grab the radial trigger and player + TELY_GameEntity *radial_trigger = TELY_Game_GetEntity(game, freya_gm->lec01_task01_radial_trigger); + TELY_GameEntity *player = TELY_Game_GetEntity(game, freya_gm->lec01_task01_player); + + Dqn_V2 player_world_pos = TELY_Game_CalcEntityWorldPos(game, player->handle); + Dqn_V2 radial_trigger_world_pos = TELY_Game_CalcEntityWorldPos(game, radial_trigger->handle); + + Dqn_f32 player_to_radial_trigger_dist_sq = Dqn_V2_LengthSq_V2x2(player_world_pos, radial_trigger_world_pos); + Dqn_Rect player_bbox = TELY_Game_CalcEntityWorldBoundingBox(game, player->handle); + + // NOTE: Sweep radius with player size (e.g. minkowski sum) + Dqn_f32 trigger_radius = freya_gm->lec01_task01_radial_trigger_radius + (player->local_hit_box_size.w * .5f); + + // NOTE: Check if we trigger the radial trigger + if (player_to_radial_trigger_dist_sq < DQN_SQUARED(trigger_radius)) { + TELY_Render_Text(renderer, + Dqn_V2_InitNx2(player_bbox.pos.x + player_bbox.size.w * .5f, player_bbox.pos.y), + Dqn_V2_InitNx2(0.5f, 1), + DQN_STRING8("!")); + } + } + + // NOTE: Lecture 01: Assignment 02 - Reflect =================================================== + { + // NOTE: Render label onto the group box + TELY_GameEntity const *group_box = TELY_Game_GetEntity(game, freya_gm->lec01_task02_group_box); + Dqn_Rect group_box_world_hit_box = TELY_Game_CalcEntityWorldHitBox(game, group_box->handle); + TELY_Render_Text(renderer, group_box_world_hit_box.pos, Dqn_V2_InitNx2(0.f, 1.f), DQN_STRING8("Assignment 02: Reflect")); + + TELY_GameEntity *ray_begin = TELY_Game_GetEntity(game, freya_gm->lec01_task02_ray_begin); + TELY_GameEntity *ray_end = TELY_Game_GetEntity(game, freya_gm->lec01_task02_ray_end); + TELY_GameEntity *surface = TELY_Game_GetEntity(game, freya_gm->lec01_task02_surface); + + Dqn_V2 ray_begin_world_pos = TELY_Game_CalcEntityWorldPos(game, ray_begin->handle); + Dqn_V2 ray_end_world_pos = TELY_Game_CalcEntityWorldPos(game, ray_end->handle); + Dqn_Rect surface_rect = TELY_Game_CalcEntityWorldBoundingBox(game, surface->handle); + Dqn_V2 rect_x0y0 = Dqn_V2_InitNx2(surface_rect.pos.x, surface_rect.pos.y); + Dqn_V2 rect_x1y0 = Dqn_V2_InitNx2(surface_rect.pos.x + surface_rect.size.w, surface_rect.pos.y); + Dqn_V2 rect_x0y1 = Dqn_V2_InitNx2(surface_rect.pos.x, surface_rect.pos.y + surface_rect.size.h); + Dqn_V2 rect_x1y1 = Dqn_V2_InitNx2(surface_rect.pos.x + surface_rect.size.h, surface_rect.pos.y + surface_rect.size.h); + + Dqn_V2 rect_x0y0_dir = rect_x1y0 - rect_x0y0; + Dqn_V2 ray_dir = ray_end_world_pos - ray_begin_world_pos; + Dqn_RaycastLineIntersectV2Result rect_top_raycast = Dqn_Raycast_LineIntersectV2(ray_begin_world_pos, ray_dir, rect_x0y0, rect_x0y0_dir); + if (rect_top_raycast.hit && rect_top_raycast.t_b >= 0 && rect_top_raycast.t_b <= 1.f && rect_top_raycast.t_a >= 0 && rect_top_raycast.t_a <= 1.f) { + Dqn_V2 intersect_p = rect_x0y0 + (rect_x0y0_dir * rect_top_raycast.t_b); + Dqn_V2 in = intersect_p - ray_begin_world_pos; + Dqn_V2 reflect = Dqn_V2_Reflect(in, rect_x0y0_dir); + TELY_Render_LineColourV4(renderer, ray_begin_world_pos, ray_end_world_pos, TELY_COLOUR_BLUE_CADET_V4, 2.f); + TELY_Render_LineColourV4(renderer, intersect_p, intersect_p + reflect, TELY_COLOUR_BLUE_CADET_V4, 2.f); + } + } +} + +extern "C" __declspec(dllexport) +void TELY_DLL_FrameUpdate(void *user_data) +{ + TELY_Platform *platform = DQN_CAST(TELY_Platform *) user_data; + TELY_PlatformInput *input = &platform->input; + TELY_Assets *assets = &platform->assets; + TELY_Renderer *renderer = &platform->renderer; + TELY_Game *game = DQN_CAST(TELY_Game *) platform->user_data; + TELY_UI *ui = &game->ui; + + TELY_UI_FrameSetup(ui, assets, &platform->frame_arena); + TELY_UI_PushFont(ui, game->jetbrains_mono_font); + + TELY_Render_ClearColourV3(renderer, TELY_COLOUR_BLACK_MIDNIGHT_V4.rgb); + TELY_Render_PushFont(renderer, game->jetbrains_mono_font); + { + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8Builder builder = {}; + builder.allocator = scratch.allocator; + + Dqn_String8Builder_AppendF(&builder, "TELY"); + if (Dqn_String8_IsValid(platform->core.os_name)) + Dqn_String8Builder_AppendF(&builder, " | %.*s", DQN_STRING_FMT(platform->core.os_name)); + + Dqn_String8Builder_AppendF(&builder, + " | %dx%d %.1fHz | TSC %.1f GHz", + platform->core.display.size.w, + platform->core.display.size.h, + platform->core.display.refresh_rate, + platform->core.tsc_per_second / 1'000'000'000.0); + + if (platform->core.ram_mb) + Dqn_String8Builder_AppendF(&builder, " | RAM %.1fGB", platform->core.ram_mb / 1024.0); + + Dqn_String8Builder_AppendF(&builder, + " | Work %04.1fms/f (%04.1f%%) | %05.1f FPS | Frame %'I64u | Timer %.1fs", + input->work_ms, + input->work_ms * 100.0 / input->delta_ms, + 1000.0 / input->delta_ms, + input->frame_counter, + input->timer_s); + + + Dqn_String8 info_label = Dqn_String8Builder_Build(&builder, scratch.allocator); + TELY_Render_Text(renderer, /*position*/ Dqn_V2_InitNx1(10), /*align*/ Dqn_V2_InitNx1(0), info_label); + } + + // ============================================================================================= + + game->prev_clicked_entity = game->clicked_entity; + game->prev_hot_entity = game->hot_entity; + game->prev_active_entity = game->active_entity; + game->hot_entity = {}; + game->active_entity = {}; + Dqn_FArray_Clear(&game->parent_entity_stack); + Dqn_FArray_Add(&game->parent_entity_stack, game->root_entity->handle); + + Dqn_M2x3 model_view = {}; + { + Dqn_V2 rotate_origin = game->camera.world_pos - (Dqn_V2_InitV2I(platform->core.window_size) * .5f); + model_view = Dqn_M2x3_Identity(); + model_view = Dqn_M2x3_Mul(model_view, Dqn_M2x3_Translate(rotate_origin)); + model_view = Dqn_M2x3_Mul(model_view, Dqn_M2x3_Rotate(game->camera.rotate_rads)); + model_view = Dqn_M2x3_Mul(model_view, Dqn_M2x3_Scale(game->camera.scale)); + model_view = Dqn_M2x3_Mul(model_view, Dqn_M2x3_Translate((rotate_origin * -1) + game->camera.world_pos)); + TELY_Render_PushTransform(renderer, model_view); + } + + Dqn_V2 world_mouse_p = Dqn_M2x3_MulV2(model_view, input->mouse_p); + + // ============================================================================================= + + TELY_Audio *audio = &platform->audio; + if (audio->playback_size == 0) { + TELY_Audio_Play(audio, game->test_audio, 1.f /*volume*/); + } + + // ============================================================================================= + + if (TELY_Platform_InputKeyWasDown(input->mouse_left) && TELY_Platform_InputKeyIsDown(input->mouse_left)) { + if (game->prev_active_entity.id) + game->active_entity = game->prev_active_entity; + } else { + for (TELY_GameEntityIterator it = {}; TELY_Game_DFSPreOrderWalkEntityTree(game, &it, game->root_entity); ) { + TELY_GameEntity *entity = it.entity; + if (entity->local_hit_box_size.x <= 0 || entity->local_hit_box_size.y <= 0) + continue; + + if ((entity->flags & TELY_EntityFlag_Clickable) == 0) + continue; + + Dqn_Rect world_hit_box = TELY_Game_CalcEntityWorldHitBox(game, entity->handle); + if (!Dqn_Rect_ContainsPoint(world_hit_box, world_mouse_p)) + continue; + + game->hot_entity = entity->handle; + if (TELY_Platform_InputKeyIsPressed(input->mouse_left)) { + game->active_entity = entity->handle; + game->clicked_entity = entity->handle; + } + } + } + + if (TELY_Platform_InputKeyIsReleased(input->mouse_left)) + game->clicked_entity = game->prev_active_entity; + + Dqn_V2 dir_vector = {}; + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_W)) + dir_vector.y = -1.f; + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_A)) + dir_vector.x = -1.f; + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_S)) + dir_vector.y = +1.f; + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_D)) + dir_vector.x = +1.f; + + if (game->clicked_entity.id) { + if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Delete)) + TELY_Game_DeleteEntity(game, game->clicked_entity); + } else { + game->camera.world_pos += dir_vector * 5.f; + } + + for (TELY_GameEntityIterator it = {}; TELY_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity); ) { + TELY_GameEntity *entity = it.entity; + entity->alive_time_s += input->delta_s; + + // NOTE: Move entity by keyboard =========================================================== + if (game->clicked_entity == entity->handle) { + if (entity->flags & TELY_EntityFlag_MoveByKeyboard) { + entity->local_pos += dir_vector * DQN_CAST(Dqn_f32)(300.f * input->delta_s); + if (dir_vector.x) + entity->facing_left = dir_vector.x < 0.f; + } + } + + // NOTE: Move entity by mouse ============================================================== + if (game->active_entity == entity->handle && entity->flags & TELY_EntityFlag_MoveByMouse) { + if (entity->flags & TELY_EntityFlag_MoveByMouse) + entity->local_pos += input->mouse_p_delta; + } + + if (entity->flags & TELY_EntityFlag_DeriveHitBoxFromChildrenBoundingBox) { + Dqn_Rect children_bbox = {}; + + // TODO(doyle): Is the hit box supposed to include the containing + // entity itself? Not sure + children_bbox.pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); + + for (TELY_GameEntityIterator child_it = {}; TELY_Game_DFSPreOrderWalkEntityTree(game, &child_it, entity);) { + TELY_GameEntity *child = child_it.entity; + DQN_ASSERT(child != entity); + + Dqn_Rect bbox = TELY_Game_CalcEntityWorldBoundingBox(game, child->handle); + children_bbox = Dqn_Rect_Union(children_bbox, bbox); + } + + Dqn_Rect padded_bbox = Dqn_Rect_Expand(children_bbox, 16.f); + entity->local_hit_box_offset = padded_bbox.pos - entity->local_pos + (padded_bbox.size * .5f); + entity->local_hit_box_size = padded_bbox.size; + } + + // NOTE: Render shapes in entity =========================================================== + Dqn_V2 world_pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); + for (TELY_GameShape const &shape_ : entity->shapes) { + TELY_GameShape const *shape = &shape_; + Dqn_V2 local_to_world_p1 = world_pos + shape->p1; + Dqn_V2 local_to_world_p2 = world_pos + shape->p2; + switch (shape->type) { + case TELY_GameShapeType_None: { + } break; + + case TELY_GameShapeType_Circle: { + TELY_Render_CircleColourV4(renderer, local_to_world_p1, shape->circle_radius, shape->render_mode, shape->colour); + } break; + + case TELY_GameShapeType_Rect: { + Dqn_Rect rect = Dqn_Rect_InitV2x2(local_to_world_p1, local_to_world_p2 - local_to_world_p1); + rect.pos -= rect.size * .5f; + TELY_Render_RectColourV4(renderer, rect, shape->render_mode, shape->colour); + } break; + + case TELY_GameShapeType_Line: { + TELY_Render_LineColourV4(renderer, local_to_world_p1, local_to_world_p2, shape->colour, shape->line_thickness); + } break; + } + } + + // NOTE: Render entity sprites ============================================================= + if (entity->sprite_sheet && entity->sprite_anims.size) { + TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet; + TELY_AssetSpriteAnimation const *sprite_anim = entity->sprite_anims.data + entity->sprite_anim_index; + uint32_t ticks_per_anim_frame = 10; + uint64_t anim_frame_counter = input->frame_counter / ticks_per_anim_frame; + + Dqn_usize sprite_index = (sprite_anim->index + (anim_frame_counter % sprite_anim->count)) % sprite_sheet->sprite_count; + 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 = {}; + 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_Rect dest_rect = {}; + dest_rect.pos = world_pos; + dest_rect.size = src_rect.size * entity->size_scale; + + if (entity->facing_left) + dest_rect.size.w *= -1.f; // NOTE: Flip the texture horizontally + + TELY_Render_TextureColourV4(renderer, sprite_sheet->tex_handle, src_rect, dest_rect, TELY_COLOUR_WHITE_V4); + } + + // NOTE: Render world position ============================================================= + TELY_Render_CircleColourV4(renderer, world_pos, 4.f, TELY_RenderShapeMode_Fill, TELY_COLOUR_RED_TOMATO_V4); + + // NOTE: Render hot/active entity ========================================================== + Dqn_Rect world_hit_box = TELY_Game_CalcEntityWorldHitBox(game, entity->handle); + if (game->clicked_entity == entity->handle) { + TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4); + } else if (game->hot_entity == entity->handle || (entity->flags & TELY_EntityFlag_DrawHitBox)) { + Dqn_V4 hot_colour = game->hot_entity == entity->handle ? TELY_COLOUR_RED_TOMATO_V4 : TELY_Colour_V4Alpha(TELY_COLOUR_YELLOW_SANDY_V4, .5f); + TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, hot_colour); + + if (game->hot_entity == entity->handle && (entity->name.size)) { + Dqn_V2 entity_world_pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8 label = Dqn_String8_InitF(scratch.allocator, + "%.*s (%.1f, %.1f)", + DQN_STRING_FMT(entity->name), + entity_world_pos.x, + entity_world_pos.y); + TELY_Render_Text(renderer, world_mouse_p, Dqn_V2_InitNx2(0.f, 1), label); + } + } + } + + // ============================================================================================= + + #if 0 + Dqn_Rect entity_ui_layout = Dqn_Rect_InitNx4(game->entity.pos.x, game->entity.pos.y, 0, 0); + TELY_UI_LayoutScope(ui, entity_ui_layout) { + TELY_UI_RectCutSideScope(ui, Dqn_RectCutSide_Bottom) { + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "Anim: %.*s [%I32u, %I32u], Frame %I64u", DQN_STRING_FMT(hero_anim_label), hero_frame_offset, hero_frame_count, hero_frame); + + TELY_UICommand *ticks_per_anim_frame_label = TELY_UI_TextF(ui, TELY_UI_GenerateID(), "Ticks Per Anim Frame: %I32u", hero_ticks_per_anim_frame); + TELY_UI_LayoutScope(ui, ticks_per_anim_frame_label->offcut_rect) { + TELY_UI_RectCutSideScope(ui, Dqn_RectCutSide_Left) { + if (TELY_UI_Button(ui, TELY_UI_GenerateID(), DQN_STRING8("-"))->interacted) { + hero_ticks_per_anim_frame--; + } + if (TELY_UI_Button(ui, TELY_UI_GenerateID(), DQN_STRING8("+"))->interacted) { + hero_ticks_per_anim_frame++; + } + } + } + + TELY_UICommand *button = TELY_UI_Button(ui, TELY_UI_GenerateID(), DQN_STRING8("Apply")); + static char count_hero_frame[3] = {}; + static Dqn_usize count_hero_frame_size = 0; + TELY_UICommand *count_input_box = TELY_UI_InputBox(ui, TELY_UI_GenerateID(), count_hero_frame, sizeof(count_hero_frame), &count_hero_frame_size); + TELY_UI_LayoutScope(ui, count_input_box->offcut_rect) { + TELY_UI_Text(ui, TELY_UI_GenerateID(), DQN_STRING8("Count")); + } + + static char start_hero_frame[3] = {}; + static Dqn_usize start_hero_frame_size = 0; + TELY_UICommand *start_sprite_input_box = TELY_UI_InputBox(ui, TELY_UI_GenerateID(), start_hero_frame, sizeof(start_hero_frame), &start_hero_frame_size); + TELY_UI_LayoutScope(ui, start_sprite_input_box->offcut_rect) { + TELY_UI_Text(ui, TELY_UI_GenerateID(), DQN_STRING8("Start Frame")); + } + + if (button->interacted) { + Dqn_String8ToU64Result start_hero_frame_u64 = Dqn_String8_ToU64(Dqn_String8_Init(start_hero_frame, start_hero_frame_size), /*separator*/ 0); + Dqn_String8ToU64Result count_hero_frame_u64 = Dqn_String8_ToU64(Dqn_String8_Init(count_hero_frame, count_hero_frame_size), /*separator*/ 0); + if (start_hero_frame_u64.success && count_hero_frame_u64.success) { + hero_frame_offset = DQN_CAST(uint32_t)start_hero_frame_u64.value; + hero_frame_count = DQN_CAST(uint32_t)count_hero_frame_u64.value; + } + } + + for (Dqn_usize anim_index = 0; anim_index < DQN_ARRAY_UCOUNT(hero_anims); anim_index++) { + TELY_AssetSpriteAnimation *anim = hero_anims + anim_index; + if (TELY_UI_Button(ui, TELY_UI_ID(__COUNTER__ + 1, DQN_CAST(uint16_t)anim_index), anim->label)->interacted) { + hero_frame_offset = anim->index; + hero_frame_count = anim->count; + hero_anim_label = anim->label; + } + } + } + } + #endif + + + // ============================================================================================= + + // NOTE: Overlay UI + Dqn_ProfilerZone profile_build_ui = Dqn_Profiler_BeginZoneWithIndex(DQN_STRING8("Build UI"), TELY_ProfileZone_BuildUI); + + #if 0 + Dqn_Rect layout = Dqn_Rect_InitNx4(100, 100, DQN_CAST(Dqn_f32)platform->window_size.w, DQN_CAST(Dqn_f32)platform->window_size.h); + TELY_UI_PushRectCutSide(ui, Dqn_RectCutSide_Top); + TELY_UI_PushLayout(ui, layout); + + TELY_UI_StringColourV4Scope(ui, Dqn_V4_InitNx4(0.8f, 0.8f, 0.8f, 1.f)) { + TELY_UI_Text(ui, TELY_UI_GenerateID(), info_label); + TELY_UI_Text(ui, TELY_UI_GenerateID(), info_label); + } + + TELY_UI_InnerPaddingScope(ui, Dqn_V2_InitNx1(0)) { + TELY_UI_OuterPaddingScope(ui, Dqn_V2_InitNx1(0)) { + TELY_UIID container_id = TELY_UI_GenerateID(); + TELY_UI_BoxScope(ui, container_id) { + for (size_t anchor_index = 1; anchor_index < TELY_ProfileZone_Count; anchor_index++) { + Dqn_ProfilerAnchor *anchor = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Front) + anchor_index; + uint64_t tsc_exclusive = anchor->tsc_exclusive; + uint64_t tsc_inclusive = anchor->tsc_inclusive; + Dqn_f64 tsc_exclusive_percentage_of_frame = tsc_exclusive * 100 / DQN_CAST(Dqn_f64)input->delta_time_tsc; + Dqn_f64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DQN_CAST(Dqn_f64)platform->tsc_per_second; + if (tsc_exclusive == tsc_inclusive) { + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "%.*s[%u]: %.1fms (%.1f%%)", + DQN_STRING_FMT(g_tely_profile_zone_names[anchor_index]), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_exclusive_percentage_of_frame); + } else { + Dqn_f64 tsc_inclusive_percentage_of_frame = tsc_inclusive * 100 / DQN_CAST(Dqn_f64)input->delta_time_tsc; + Dqn_f64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DQN_CAST(Dqn_f64)platform->tsc_per_second; + + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "%.*s[%u]: %.1f/%.1fms (%.1f/%.1f%%)", + DQN_STRING_FMT(g_tely_profile_zone_names[anchor_index]), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_inclusive_milliseconds, + tsc_exclusive_percentage_of_frame, + tsc_inclusive_percentage_of_frame); + } + } + } + + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "mouse_p={%.2f, %.2f}", input->mouse_p.x, input->mouse_p.y); + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "mouse_p_delta={%.2f, %.2f}", input->mouse_p_delta.x, input->mouse_p_delta.y); + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "mouse_wheel={%.2f, %.2f}", input->mouse_wheel.x, input->mouse_wheel.y); + + TELY_PlatformInputKey *left = input->scan_codes + TELY_PlatformInputScanCode_Left; + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "left={transition_count=%u}", left->transition_count); + } + } + + if (TELY_UI_Button(ui, TELY_UI_GenerateID(), DQN_STRING8("Hello world, long button"))->interacted) { + Dqn_Log_DebugF("Button 2 clicked!"); + } + + // TODO(doyle): There's some padding issues here + static Dqn_f32 f32_val = {}; + static Dqn_f64 f64_val = {}; + TELY_UI_BeginBox(ui, TELY_UI_GenerateID()); + TELY_UI_RectCutSideScope(ui, Dqn_RectCutSide_Left) { + TELY_UI_DragBoxF32(ui, TELY_UI_GenerateID(), &f32_val); + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "F32: %.1f", f32_val); + } + TELY_UI_EndBox(ui, TELY_UI_GenerateID()); + + TELY_UI_BeginBox(ui, TELY_UI_GenerateID()); + TELY_UI_RectCutSideScope(ui, Dqn_RectCutSide_Left) { + TELY_UI_DragBoxF64(ui, TELY_UI_GenerateID(), &f64_val); + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "F64: %.1f", f64_val); + } + TELY_UI_EndBox(ui, TELY_UI_GenerateID()); + + TELY_UI_Text(ui, TELY_UI_GenerateID(), DQN_STRING8("Draw")); + TELY_UI_BeginBox(ui, TELY_UI_GenerateID()); + TELY_UI_RectCutSideScope(ui, Dqn_RectCutSide_Left) { + TELY_UI_Checkbox(ui, TELY_UI_GenerateID(), &ui->debug_draw_bounding_rect); + TELY_UI_Text(ui, TELY_UI_GenerateID(), DQN_STRING8("Bounding Rect")); + } + TELY_UI_EndBox(ui, TELY_UI_GenerateID()); + + TELY_UI_BeginBox(ui, TELY_UI_GenerateID()); + TELY_UI_RectCutSideScope(ui, Dqn_RectCutSide_Left) { + TELY_UI_Checkbox(ui, TELY_UI_GenerateID(), &ui->debug_draw_clip_rect); + TELY_UI_Text(ui, TELY_UI_GenerateID(), DQN_STRING8("Clip Rect")); + } + TELY_UI_EndBox(ui, TELY_UI_GenerateID()); + + TELY_UI_BeginBox(ui, TELY_UI_GenerateID()); + TELY_UI_RectCutSideScope(ui, Dqn_RectCutSide_Left) { + TELY_UI_Checkbox(ui, TELY_UI_GenerateID(), &ui->debug_draw_content_rect); + TELY_UI_Text(ui, TELY_UI_GenerateID(), DQN_STRING8("Content Rect")); + } + TELY_UI_EndBox(ui, TELY_UI_GenerateID()); + + TELY_UI_BeginBox(ui, TELY_UI_GenerateID()); + TELY_UI_RectCutSideScope(ui, Dqn_RectCutSide_Left) { + TELY_UI_Checkbox(ui, TELY_UI_GenerateID(), &ui->debug_draw_client_rect); + TELY_UI_Text(ui,TELY_UI_GenerateID(), DQN_STRING8("Client Rect")); + } + TELY_UI_EndBox(ui, TELY_UI_GenerateID()); + + static char buffer[16] = "012345678"; + static Dqn_usize buffer_text_size = 9; + TELY_UI_InputBox(ui, TELY_UI_GenerateID(), buffer, sizeof(buffer), &buffer_text_size); + #endif + + #if 0 + // TODO(doyle): Drawing widgets after this window will draw it relative + // to the window's origin. + static Dqn_Rect window_layout = Dqn_Rect_InitNx4(100, 100, 300, 300); + TELY_UI_BeginWindow(ui, TELY_UI_GenerateID(), DQN_STRING8("TSC Profiler"), &window_layout); + // TELY_UI_PushInnerPadding(ui, Dqn_V2_InitNx1(0)); + // TELY_UI_PushOuterPadding(ui, Dqn_V2_InitNx1(0)); + for (size_t anchor_index = 1; anchor_index < TELY_ProfileZone_Count; anchor_index++) { + Dqn_ProfilerAnchor *anchor = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Front) + anchor_index; + uint64_t tsc_exclusive = anchor->tsc_exclusive; + uint64_t tsc_inclusive = anchor->tsc_inclusive; + Dqn_f64 tsc_exclusive_percentage_of_frame = tsc_exclusive * 100 / DQN_CAST(Dqn_f64)input->delta_time_tsc; + Dqn_f64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DQN_CAST(Dqn_f64)platform->tsc_per_second; + Dqn_String8 zone_name = g_tely_profile_zone_names[anchor_index]; + if (tsc_exclusive == tsc_inclusive) { + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "%.*s[%u]: %.1fms (%.1f%%)", + DQN_STRING_FMT(zone_name), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_exclusive_percentage_of_frame); + } else { + Dqn_f64 tsc_inclusive_percentage_of_frame = tsc_inclusive * 100 / DQN_CAST(Dqn_f64)input->delta_time_tsc; + Dqn_f64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DQN_CAST(Dqn_f64)platform->tsc_per_second; + + TELY_UI_TextF(ui, TELY_UI_GenerateID(), "%.*s[%u]: %.1f/%.1fms (%.1f/%.1f%%)", + DQN_STRING_FMT(zone_name), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_inclusive_milliseconds, + tsc_exclusive_percentage_of_frame, + tsc_inclusive_percentage_of_frame); + } + } + // TELY_UI_PopInnerPadding(ui); + // TELY_UI_PopOuterPadding(ui); + TELY_UI_EndWindow(ui, TELY_UI_GenerateID()); + TELY_UI_Flush(ui, platform, renderer, frame_time); + #endif + + // ============================================================================================= + + TELY_RFui *rfui = &game->rfui; + TELY_RFui_FrameSetup(rfui, &platform->frame_arena); + TELY_RFui_PushFont(rfui, game->jetbrains_mono_font); + #if 0 + + TELY_RFuiResult row = TELY_RFui_Row(rfui, DQN_STRING8("Row ID")); + TELY_RFui_PushParent(rfui, row.widget); + if (TELY_RFui_Button(rfui, DQN_STRING8("File")).clicked) { + Dqn_Log_DebugF("Hello Seaman"); + } + if (TELY_RFui_Button(rfui, DQN_STRING8("Window")).clicked) { + Dqn_Log_DebugF("Hello Seaman"); + } + if (TELY_RFui_Button(rfui, DQN_STRING8("Panel")).clicked) { + Dqn_Log_DebugF("Hello Seaman"); + } + TELY_RFui_PopParent(rfui); + #endif + + // ============================================================================================= + + TELY_DLL_FreyaMathForGameDevs(platform); + + // ============================================================================================= + + TELY_Audio_MixPlaybackSamples(audio, assets); + + // TELY_RFui_Flush(rfui, renderer, input, assets); + Dqn_Profiler_EndZone(profile_build_ui); +} diff --git a/feely_pona_unity.cpp b/feely_pona_unity.cpp new file mode 100644 index 0000000..09b85fb --- /dev/null +++ b/feely_pona_unity.cpp @@ -0,0 +1 @@ +#include "tely_dll_unity.h" diff --git a/feely_pona_unity.h b/feely_pona_unity.h new file mode 100644 index 0000000..27001ba --- /dev/null +++ b/feely_pona_unity.h @@ -0,0 +1,63 @@ +// ================================================================================================= + +// NOTE(doyle): Work-around for clangd to correctly resolve symbols in unity +// builds by providing symbol definition and prototypes by including this +// mega-header in all files and using the "#pragma once" directive to +// avoid multiple defined symbols when compiling the unity build itself. +// +// See: https://www.frogtoss.com/labs/clangd-with-unity-builds.html + +#pragma once + +// NOTE: DQN ======================================================================================= + +// NOTE: C-strings declared in a ternary cause global-buffer-overflow in +// MSVC2022. +// stb_sprintf assumes c-string literals are 4 byte aligned which is always +// true, however, reading past the end of a string whose size is not a multiple +// of 4 is UB causing ASAN to complain. +#if defined(_MSC_VER) && !defined(__clang__) + #define STBSP__ASAN __declspec(no_sanitize_address) +#endif + +#define DQN_ASAN_POISON 0 +#define DQN_ASAN_VET_POISON 1 +#define DQN_ONLY_RECT +#define DQN_ONLY_V2 +#define DQN_ONLY_V3 +#define DQN_ONLY_V4 +#define DQN_ONLY_WIN +#define DQN_ONLY_FARRAY +#define DQN_ONLY_PROFILER +#define DQN_ONLY_SLICE +#define DQN_ONLY_LIST +#define DQN_ONLY_VARRAY +#define DQN_ONLY_FS +#define _CRT_SECURE_NO_WARNINGS +#define DQN_IMPLEMENTATION +#include "external/dqn/dqn.h" + +// NOTE: TELY ====================================================================================== + +DQN_MSVC_WARNING_DISABLE(4505) // warning C4505: unreferenced function with internal linkage has been removed +#include "tely_profile.h" +#include "tely_platform_input.h" +#include "tely_asset.h" +#include "tely_colour.h" +#include "tely_render.h" +#include "tely_tools.h" +#include "tely_audio.h" +#include "tely_platform.h" +#include "tely_ui.h" +#include "tely_rfui.h" +#include "tely_game.h" + +#include "tely_tools.cpp" +#include "tely_game.cpp" +#include "tely_asset.cpp" +#include "tely_audio.cpp" +#include "tely_render.cpp" +#include "tely_platform_input.cpp" +#include "tely_ui.cpp" +#include "tely_rfui.cpp" +#include "tely_dll.cpp"