diff --git a/apps/demos/platformer/player.c b/apps/demos/platformer/player.c index bb62eb4..f71d2e5 100644 --- a/apps/demos/platformer/player.c +++ b/apps/demos/platformer/player.c @@ -11,9 +11,9 @@ static void update_timers(Player *player) { - tick_timer(&player->jump_air_timer); - tick_timer(&player->jump_coyote_timer); - tick_timer(&player->jump_buffer_timer); + player->jump_air_timer = timer_tick_frames(player->jump_air_timer); + player->jump_coyote_timer = timer_tick_frames(player->jump_coyote_timer); + player->jump_buffer_timer = timer_tick_frames(player->jump_buffer_timer); } diff --git a/apps/demos/platformer/world.c b/apps/demos/platformer/world.c index 067b144..0234bc2 100644 --- a/apps/demos/platformer/world.c +++ b/apps/demos/platformer/world.c @@ -39,8 +39,8 @@ static void drawdef_debug(struct World *world) { for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) { if (world->tiles[i].type == TILE_TYPE_VOID) continue; - draw_rectangle(to_frect(world->tiles[i].rect), - (Color) { 255, 0, 255, 128 }); + draw_rectangle(to_rect(world->tiles[i].rect), + (Color) { 255, 0, 255, 128 }); } } @@ -106,7 +106,7 @@ void world_drawdef(struct World *world) { if (world->tiles[i].type == TILE_TYPE_VOID) continue; - m_sprite("/assets/white.png", to_frect(world->tiles[i].rect)); + m_sprite("/assets/white.png", to_rect(world->tiles[i].rect)); } drawdef_debug(world); @@ -121,19 +121,12 @@ bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersecti if (world->tiles[i].type == TILE_TYPE_VOID) continue; - Rect tile_frect = { - .x = (float)(world->tiles[i].rect.x), - .y = (float)(world->tiles[i].rect.y), - .w = (float)(world->tiles[i].rect.w), - .h = (float)(world->tiles[i].rect.h), - }; + Rect const tile_frect = to_rect(world->tiles[i].rect); - if (intersection == NULL) { - Rect temp; - is_intersecting = overlap_frect(&rect, &tile_frect, &temp); - } else { - is_intersecting = overlap_frect(&rect, &tile_frect, intersection); - } + is_intersecting = intersect_rect(rect, tile_frect); + + if (intersection) + *intersection = overlap_rect(rect, tile_frect); if (is_intersecting) break; @@ -151,14 +144,12 @@ bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersect if (world->tiles[i].type == TILE_TYPE_VOID) continue; - Recti *tile_rect = &world->tiles[i].rect; + Recti const tile_rect = world->tiles[i].rect; - if (intersection == NULL) { - Recti temp; - is_intersecting = overlap_rect(&rect, tile_rect, &temp); - } else { - is_intersecting = overlap_rect(&rect, tile_rect, intersection); - } + is_intersecting = intersect_recti(rect, tile_rect); + + if (intersection) + *intersection = overlap_recti(rect, tile_rect); if (is_intersecting) break; diff --git a/docs/interop.md b/docs/interop.md index 45ae1bc..4baba6a 100644 --- a/docs/interop.md +++ b/docs/interop.md @@ -3,11 +3,13 @@ api needs to facilitate easy interoperability with other languages and tools, for that certain considerations are taken: * number of public api calls is kept at the minimum -* procedure signatures can only use basic types, no aggregates, with exception of Vec/Matrix types and alike, +* procedure parameters can only use basic types, no aggregates, with exception of Vec/Matrix types and alike, with no expectation on new additions (see [/include/twn_types.h](/include/twn_types.h)) * optionals can be expressed via pointer passage of value primitives, with NULL expressive default, but they should be immutable -* opaque types are passed around as pointers +* opaque typed parameters are passed around as pointers * when mutation on input is done, - it shouldn't be achieved from a mutable pointer, but return value +* return value could be a simple aggregate that is translatable to value-only dictionary * module prefix is used for namespacing, actual symbols come after the prefix (`module_symbol`) * symbols should not contain letters at the start nor after the namespace prefix +* floats are preferred over integers * [/include/twn_api.json](/include/twn_api.json) file is hand-kept with a schema to aid automatic binding generation and other tooling diff --git a/include/twn_util.h b/include/twn_util.h index 454825c..4ee74c3 100644 --- a/include/twn_util.h +++ b/include/twn_util.h @@ -24,74 +24,67 @@ TWN_API void *crealloc(void *ptr, size_t size); TWN_API void *ccalloc(size_t num, size_t size); + TWN_API void log_info(const char *restrict format, ...); + TWN_API void log_critical(const char *restrict format, ...); + TWN_API void log_warn(const char *restrict format, ...); + + /* saves all texture atlases as BMP files in the write directory */ + TWN_API void textures_dump_atlases(void); + + /* returns true if str ends with suffix */ + TWN_API bool strends(const char *str, const char *suffix); + + /* TODO: this is why generics were invented. sorry, i'm tired today */ + TWN_API double clamp(double d, double min, double max); + TWN_API float clampf(float f, float min, float max); + TWN_API int clampi(int i, int min, int max); + + /* sets buf_out to a pointer to a byte buffer which must be freed. */ + /* returns the size of this buffer. */ + TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out); + + /* returns a pointer to a string which must be freed */ + TWN_API char *file_to_str(const char *path); + + /* returns true if the file exists in the filesystem */ + TWN_API bool file_exists(const char *path); + #endif /* TWN_NOT_C */ - -TWN_API void log_info(const char *restrict format, ...); -TWN_API void log_critical(const char *restrict format, ...); -TWN_API void log_warn(const char *restrict format, ...); - - -/* TODO: this is why generics were invented. sorry, i'm tired today */ -TWN_API double clamp(double d, double min, double max); -TWN_API float clampf(float f, float min, float max); -TWN_API int clampi(int i, int min, int max); - - -/* sets buf_out to a pointer to a byte buffer which must be freed. */ -/* returns the size of this buffer. */ -TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out); - -/* returns a pointer to a string which must be freed */ -TWN_API char *file_to_str(const char *path); - -/* returns true if the file exists in the filesystem */ -TWN_API bool file_exists(const char *path); - - -/* saves all texture atlases as BMP files in the write directory */ -TWN_API void textures_dump_atlases(void); - - -/* returns true if str ends with suffix */ -TWN_API TWN_API bool strends(const char *str, const char *suffix); - - -/* */ -/* GAME LOGIC UTILITIES */ -/* */ - -/* calculates the overlap of two rectangles and places it in result. */ -/* result may be NULL. if this is the case, it will simply be ignored. */ -/* returns true if the rectangles are indeed intersecting. */ -TWN_API bool overlap_rect(const Recti *a, const Recti *b, Recti *result); -TWN_API bool overlap_frect(const Rect *a, const Rect *b, Rect *result); +/* calculates the overlap of two rectangles */ +TWN_API Recti overlap_recti(const Recti a, const Recti b); +TWN_API Rect overlap_rect(const Rect a, const Rect b); /* returns true if two rectangles are intersecting */ -TWN_API bool intersect_rect(const Recti *a, const Recti *b); -TWN_API bool intersect_frect(const Rect *a, const Rect *b); +TWN_API bool intersect_recti(const Recti a, const Recti b); +TWN_API bool intersect_rect(const Rect a, const Rect b); /* TODO: generics and specials (see m_vec2_from() for an example)*/ -TWN_API Rect to_frect(Recti rect); +TWN_API Recti to_recti(Rect rect); +TWN_API Rect to_rect(Recti rect); -TWN_API Vec2 frect_center(Rect rect); +TWN_API Vec2i center_recti(Recti rect); +TWN_API Vec2 center_rect(Rect rect); - -/* decrements an lvalue (which should be an int), stopping at 0 */ +/* decrements an integer value, stopping at 0 */ /* meant for tick-based timers in game logic */ /* * example: * tick_timer(&player->jump_air_timer); */ -TWN_API void tick_timer(int *value); +TWN_API int32_t timer_tick_frames(int32_t frames_left); -/* decrements a floating point second-based timer, stopping at 0.0 */ +/* decrements a floating point second-based timer, stopping at 0.0f */ /* meant for poll based real time logic in game logic */ /* note that it should be decremented only on the next tick after its creation */ -TWN_API void tick_ftimer(float *value); +TWN_API float timer_tick_seconds(float seconds_left); -/* same as `tick_ftimer` but instead of clamping it repeats */ -/* returns true if value was cycled */ -TWN_API bool repeat_ftimer(float *value, float at); +TWN_API struct timer_elapse_frames_result { + bool elapsed; int32_t frames_left; +} timer_elapse_frames(int32_t frames_left, int32_t interval); + +TWN_API struct timer_elapse_seconds_result { + bool elapsed; float seconds_left; +} timer_elapse_seconds(float seconds_left, float interval); #endif diff --git a/src/rendering/twn_sprites.c b/src/rendering/twn_sprites.c index 0567dfe..9211c81 100644 --- a/src/rendering/twn_sprites.c +++ b/src/rendering/twn_sprites.c @@ -216,7 +216,7 @@ void render_sprite_batch(const Primitive2D primitives[], #pragma GCC diagnostic pop - const Vec2 c = frect_center(sprite.rect); + const Vec2 c = center_rect(sprite.rect); const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4); const Vec2 d = { .x = t.x * sprite.rect.w * (float)M_SQRT1_2, @@ -231,7 +231,7 @@ void render_sprite_batch(const Primitive2D primitives[], } else { /* rotated non-square case*/ - const Vec2 c = frect_center(sprite.rect); + const Vec2 c = center_rect(sprite.rect); const Vec2 t = fast_cossine(sprite.rotation); const Vec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 }; diff --git a/src/twn_util.c b/src/twn_util.c index d458a6a..7f02e68 100644 --- a/src/twn_util.c +++ b/src/twn_util.c @@ -197,49 +197,57 @@ bool strends(const char *str, const char *suffix) { } -bool overlap_rect(const Recti *a, const Recti *b, Recti *result) { - SDL_Rect a_sdl = { a->x, a->y, a->w, a->h }; - SDL_Rect b_sdl = { b->x, b->y, b->w, b->h }; +/* TODO: have our own */ +Recti overlap_recti(const Recti a, const Recti b) { + SDL_Rect a_sdl = { a.x, a.y, a.w, a.h }; + SDL_Rect b_sdl = { b.x, b.y, b.w, b.h }; SDL_Rect result_sdl = { 0 }; - bool intersection = SDL_IntersectRect(&a_sdl, &b_sdl, &result_sdl); + (void)SDL_IntersectRect(&a_sdl, &b_sdl, &result_sdl); - if (result != NULL) - *result = (Recti){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h }; - - return intersection; + return (Recti){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h }; } -bool overlap_frect(const Rect *a, const Rect *b, Rect *result) { - SDL_FRect a_sdl = { a->x, a->y, a->w, a->h }; - SDL_FRect b_sdl = { b->x, b->y, b->w, b->h }; +/* TODO: have our own */ +Rect overlap_rect(const Rect a, const Rect b) { + SDL_FRect a_sdl = { a.x, a.y, a.w, a.h }; + SDL_FRect b_sdl = { b.x, b.y, b.w, b.h }; SDL_FRect result_sdl = { 0 }; - bool intersection = SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl); + (void)SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl); - if (result != NULL) - *result = (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h }; - - return intersection; + return (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h }; } -bool intersect_rect(const Recti *a, const Recti *b) { - SDL_Rect a_sdl = { a->x, a->y, a->w, a->h }; - SDL_Rect b_sdl = { b->x, b->y, b->w, b->h }; +/* TODO: have our own */ +bool intersect_recti(const Recti a, const Recti b) { + SDL_Rect a_sdl = { a.x, a.y, a.w, a.h }; + SDL_Rect b_sdl = { b.x, b.y, b.w, b.h }; return SDL_HasIntersection(&a_sdl, &b_sdl); } -bool intersect_frect(const Rect *a, const Rect *b) { - SDL_FRect a_sdl = { a->x, a->y, a->w, a->h }; - SDL_FRect b_sdl = { b->x, b->y, b->w, b->h }; +/* TODO: have our own */ +bool intersect_rect(const Rect a, const Rect b) { + SDL_FRect a_sdl = { a.x, a.y, a.w, a.h }; + SDL_FRect b_sdl = { b.x, b.y, b.w, b.h }; return SDL_HasIntersectionF(&a_sdl, &b_sdl); } -Rect to_frect(Recti rect) { +Recti to_recti(Rect rect) { + return (Recti) { + .h = (int32_t)rect.h, + .w = (int32_t)rect.w, + .x = (int32_t)rect.x, + .y = (int32_t)rect.y, + }; +} + + +Rect to_rect(Recti rect) { return (Rect) { .h = (float)rect.h, .w = (float)rect.w, @@ -249,7 +257,15 @@ Rect to_frect(Recti rect) { } -Vec2 frect_center(Rect rect) { +Vec2i center_recti(Recti rect) { + return (Vec2i){ + .x = rect.x + rect.w / 2, + .y = rect.y + rect.h / 2, + }; +} + + +Vec2 center_rect(Rect rect) { return (Vec2){ .x = rect.x + rect.w / 2, .y = rect.y + rect.h / 2, @@ -257,25 +273,52 @@ Vec2 frect_center(Rect rect) { } -void tick_timer(int *value) { - *value = MAX(*value - 1, 0); +int32_t timer_tick_frames(int32_t frames_left) { + SDL_assert(frames_left >= 0); + return MAX(frames_left - 1, 0); } -void tick_ftimer(float *value) { - *value = MAX(*value - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f); +float timer_tick_seconds(float seconds_left) { + SDL_assert(seconds_left >= 0); + return MAX(seconds_left - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f); } -bool repeat_ftimer(float *value, float at) { - *value -= (float)ctx.delta_time / (float)ctx.clocks_per_second; - if (*value < 0.0f) { - *value += at; - return true; +struct timer_elapse_frames_result timer_elapse_frames(int32_t frames_left, int32_t interval) { + SDL_assert(frames_left >= 0); + SDL_assert(interval > 0); + + frames_left -= 1; + bool elapsed = false; + if (frames_left <= 0) { + elapsed = true; + frames_left += interval; } - return false; + return (struct timer_elapse_frames_result) { + .elapsed = elapsed, + .frames_left = frames_left + }; } + +struct timer_elapse_seconds_result timer_elapse_seconds(float seconds_left, float interval) { + SDL_assert(seconds_left >= 0); + SDL_assert(interval > 0); + + seconds_left -= (float)ctx.delta_time / (float)ctx.clocks_per_second; + bool elapsed = false; + if (seconds_left <= 0.0f) { + elapsed = true; + seconds_left += interval; + } + return (struct timer_elapse_seconds_result) { + .elapsed = elapsed, + .seconds_left = seconds_left + }; +} + + /* TODO: handle utf8 */ char *expand_asterisk(const char *mask, const char *to) { const char *offset = SDL_strchr(mask, '*');