2025-08-08 22:39:48 -04:00
|
|
|
#include <limits.h>
|
|
|
|
|
#include <stdlib.h>
|
2026-05-24 09:51:58 -04:00
|
|
|
#include <errno.h>
|
2026-05-24 19:57:43 -04:00
|
|
|
#include <libgen.h>
|
2025-08-08 22:39:48 -04:00
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
|
#include <SDL3_image/SDL_image.h>
|
2026-01-05 08:58:06 -05:00
|
|
|
#include <akerror.h>
|
2025-08-03 10:41:09 -04:00
|
|
|
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/util.h>
|
|
|
|
|
#include <akgl/heap.h>
|
|
|
|
|
#include <akgl/registry.h>
|
|
|
|
|
#include <akgl/game.h>
|
2026-05-24 09:51:58 -04:00
|
|
|
#include <akgl/staticstring.h>
|
|
|
|
|
|
|
|
|
|
#include <akstdlib.h>
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext *akgl_path_relative_root(char *root, char *path, akgl_String *dst)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
akgl_String *pathbuf;
|
|
|
|
|
akgl_String *strbuf;
|
|
|
|
|
char *result;
|
|
|
|
|
int rootlen;
|
|
|
|
|
int pathlen;
|
|
|
|
|
int count;
|
|
|
|
|
|
|
|
|
|
FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
|
|
|
|
|
PASS(e, akgl_heap_next_string(&strbuf));
|
|
|
|
|
PASS(e, akgl_heap_next_string(&pathbuf));
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
// Is it relative to the root?
|
|
|
|
|
rootlen = strlen(root);
|
|
|
|
|
pathlen = strlen(path);
|
|
|
|
|
if ( (rootlen + pathlen) >= AKGL_MAX_STRING_LENGTH ) {
|
|
|
|
|
FAIL_RETURN(e, AKERR_OUTOFBOUNDS, "Total path length (%d) is greater than maximum akgl_String length (%d)", (rootlen + pathlen), AKGL_MAX_STRING_LENGTH);
|
|
|
|
|
}
|
2026-05-24 19:57:43 -04:00
|
|
|
DISABLE_GCC_WARNING_FORMAT_TRUNCATION
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(e, aksl_sprintf(&count, (char *)&pathbuf->data, "%s/%s", root, path));
|
2026-05-24 19:57:43 -04:00
|
|
|
RESTORE_GCC_WARNINGS
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(e, aksl_realpath((char *)&pathbuf->data, (char *)&strbuf->data));
|
|
|
|
|
CATCH(e, akgl_string_copy(strbuf, dst, 0));
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(akgl_heap_release_string(strbuf));
|
|
|
|
|
IGNORE(akgl_heap_release_string(pathbuf));
|
|
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH(e, true);
|
|
|
|
|
SUCCEED_RETURN(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext *akgl_path_relative(char *root, char *path, akgl_String *dst)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
akgl_String *strbuf;
|
|
|
|
|
char *result;
|
|
|
|
|
|
|
|
|
|
FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
|
|
|
|
|
PASS(e, akgl_heap_next_string(&strbuf));
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
// Is path relative to our current working directory?
|
|
|
|
|
CATCH(e, aksl_realpath(path, (char *)&strbuf->data));
|
|
|
|
|
// Yes it is. strbuf->data contains the absolute path.
|
|
|
|
|
CATCH(e, akgl_string_copy(strbuf, dst, 0));
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(akgl_heap_release_string(strbuf));
|
|
|
|
|
} PROCESS(e) {
|
|
|
|
|
} HANDLE(e, ENOENT) {
|
|
|
|
|
// Path is not relative to our current working directory
|
|
|
|
|
// Noop - execution proceeds after the break
|
|
|
|
|
return akgl_path_relative_root(root, path, dst);
|
|
|
|
|
} FINISH(e, true);
|
|
|
|
|
SUCCEED_RETURN(e);
|
|
|
|
|
}
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-24 19:57:43 -04:00
|
|
|
akerr_ErrorContext *akgl_path_relative_from(char *path, char *from, akgl_String **dst)
|
|
|
|
|
{
|
|
|
|
|
akgl_String *dirnamestr;
|
|
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "path");
|
|
|
|
|
FAIL_ZERO_RETURN(e, from, AKERR_NULLPOINTER, "from");
|
|
|
|
|
PASS(e, akgl_heap_next_string(&dirnamestr));
|
|
|
|
|
PASS(e, aksl_realpath(from, (char *)&dirnamestr->data));
|
|
|
|
|
dirname((char *)&dirnamestr->data);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(e);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_rectangle_points(RectanglePoints *dest, SDL_FRect *rect)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL RectanglePoints reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "NULL Rectangle reference");
|
2025-08-03 10:07:35 -04:00
|
|
|
dest->topleft.x = rect->x;
|
|
|
|
|
dest->topleft.y = rect->y;
|
|
|
|
|
dest->bottomleft.x = rect->x;
|
|
|
|
|
dest->bottomleft.y = rect->y + rect->h;
|
|
|
|
|
dest->topright.x = rect->x + rect->w;
|
|
|
|
|
dest->topright.y = rect->y;
|
|
|
|
|
dest->bottomright.x = rect->x + rect->w;
|
|
|
|
|
dest->bottomright.y = rect->y + rect->h;
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_collide_point_rectangle(point *p, RectanglePoints *rp, bool *collide)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, p, AKERR_NULLPOINTER, "NULL Point reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, rp, AKERR_NULLPOINTER, "NULL RectanglePoints reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL boolean reference");
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( (p->x >= rp->topleft.x) && (p->y >= rp->topleft.y) &&
|
|
|
|
|
(p->x <= rp->bottomright.x) && (p->y <= rp->bottomright.y) ) {
|
|
|
|
|
*collide = true;
|
|
|
|
|
} else {
|
|
|
|
|
*collide = false;
|
|
|
|
|
}
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
RectanglePoints r1p;
|
|
|
|
|
RectanglePoints r2p;
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, r1, AKERR_NULLPOINTER, "NULL rectangle reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, r2, AKERR_NULLPOINTER, "NULL rectangle reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL collision flag reference");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_rectangle_points(&r1p, r1));
|
|
|
|
|
CATCH(errctx, akgl_rectangle_points(&r2p, r2));
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
// is the upper left corner of r1 contacting r2?
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_point_rectangle(&r1p.topleft, &r2p, collide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
|
|
|
|
|
|
|
|
|
// is the upper left corner of r2 contacting r1?
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_point_rectangle(&r2p.topleft, &r1p, collide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
|
|
|
|
|
|
|
|
|
// is the top right corner of r1 contacting r2?
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_point_rectangle(&r1p.topright, &r2p, collide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
|
|
|
|
|
|
|
|
|
// is the top right corner of r2 contacting r1?
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_point_rectangle(&r2p.topright, &r1p, collide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
|
|
|
|
|
|
|
|
|
// is the bottom left corner of r1 contacting r2?
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_point_rectangle(&r1p.bottomleft, &r2p, collide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
|
|
|
|
|
|
|
|
|
// is the bottom left corner of r2 contacting r1?
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_point_rectangle(&r2p.bottomleft, &r1p, collide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
|
|
|
|
|
|
|
|
|
// is the bottom right corner of r1 contacting r2?
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_point_rectangle(&r1p.bottomright, &r2p, collide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
|
|
|
|
|
|
|
|
|
// is the bottom right corner of r2 contacting r1?
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_point_rectangle(&r2p.bottomright, &r1p, collide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
|
|
|
|
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
*collide = false;
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_compare_sdl_surfaces(SDL_Surface *s1, SDL_Surface *s2)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, s1, AKERR_NULLPOINTER, "NULL Surface pointer");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, s2, AKERR_NULLPOINTER, "NULL Surface pointer");
|
|
|
|
|
FAIL_NONZERO_RETURN(errctx, memcmp(s1->pixels, s2->pixels, (s1->pitch * s1->h)), AKERR_VALUE, "Comparison surfaces are not equal");
|
2025-08-03 10:07:35 -04:00
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, int x, int y, int w, int h, char *writeout)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
SDL_Surface *s1 = NULL;
|
|
|
|
|
SDL_Surface *s2 = NULL;
|
|
|
|
|
SDL_FRect src = {.x = x, .y = y, .w = w, .h = h};
|
|
|
|
|
SDL_FRect dest = {.x = x, .y = y, .w = w, .h = h};
|
|
|
|
|
SDL_Rect read = {.x = x, .y = y, .w = w, .h = h};
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_String *tmpstring = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_BREAK(errctx, t1, AKERR_NULLPOINTER, "NULL texture");
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, t2, AKERR_NULLPOINTER, "NULL texture");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_string(&tmpstring));
|
2026-05-24 21:59:29 -04:00
|
|
|
SDL_RenderClear(renderer.sdl_renderer);
|
|
|
|
|
CATCH(errctx, renderer.draw_texture(&renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
|
|
|
|
|
s1 = SDL_RenderReadPixels(renderer.sdl_renderer, &read);
|
2026-05-06 23:18:42 -04:00
|
|
|
FAIL_ZERO_BREAK(errctx, s1, AKGL_ERR_SDL, "Failed to read pixels from renderer");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
if ( writeout != NULL ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
snprintf((char *)&tmpstring->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), writeout);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
IMG_SavePNG(s1, (char *)&tmpstring->data),
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_IO,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Unable to save %s: %s",
|
|
|
|
|
(char *)&tmpstring->data,
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 21:59:29 -04:00
|
|
|
SDL_RenderClear(renderer.sdl_renderer);
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-24 21:59:29 -04:00
|
|
|
CATCH(errctx, renderer.draw_texture(&renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
|
|
|
|
|
s2 = SDL_RenderReadPixels(renderer.sdl_renderer, &read);
|
2026-05-06 23:18:42 -04:00
|
|
|
FAIL_ZERO_BREAK(errctx, s2, AKGL_ERR_SDL, "Failed to read pixels from renderer");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_compare_sdl_surfaces(s1, s2));
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
if ( s1 != NULL )
|
|
|
|
|
SDL_DestroySurface(s1);
|
|
|
|
|
if ( s2 != NULL )
|
|
|
|
|
SDL_DestroySurface(s2);
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_string(tmpstring));
|
2025-08-03 10:07:35 -04:00
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|