akgl_rectangle_points, akgl_collide_point_rectangle, akgl_Point and akgl_RectanglePoints go. They were the intermediate form of an implementation that changed: akgl_collide_rectangles was eight corner-containment tests built on them, and it has been four span comparisons since the cross-case fix. Nothing outside tests/ called either function, and a point-in-rectangle test is four comparisons a caller can write without a struct conversion in front of them. akgl_collide_rectangles stays. It has two correct callers in the sidescroller asking a game-level overlap question -- a coin, a hazard, from an updatefunc -- where a bool is the whole answer and a proxy plus a narrowphase call would be computing a normal nothing reads. TODO.md records the split rather than leaving it to be rediscovered. Public API removal, so 194 exported akgl_ symbols against 196, and the manual's counts move with them. The perf suite loses its rectangle_points row; the all-pairs sweep stays as the control it is now labelled, and PERFORMANCE.md says what 0.8.0 measured against it -- 188.5 us for 32,640 pairs at 256 actors, where a whole step with collision attached is 54.1 us doing strictly more. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
147 lines
7.5 KiB
C
147 lines
7.5 KiB
C
/**
|
|
* @file util.h
|
|
* @brief A rectangle overlap test, path resolution, and two test-only image helpers.
|
|
*
|
|
* The grab bag. Three unrelated groups live here: one rectangle overlap test,
|
|
* path resolution for the asset loaders, and a pair of pixel-comparison routines
|
|
* that exist only so tests can assert on what was actually drawn.
|
|
*
|
|
* akgl_collide_rectangles is axis-aligned and treats edges as touching: two
|
|
* rectangles sharing an edge and nothing more overlap. It answers a *game's*
|
|
* question -- a pickup, a minimap marker, a UI hit test -- and is deliberately
|
|
* not what the physics step uses. Anything that should push or be pushed wants a
|
|
* collision shape and akgl_collision_test; see `akgl/collision.h`.
|
|
*/
|
|
|
|
#ifndef _AKGL_UTIL_H_
|
|
#define _AKGL_UTIL_H_
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <akerror.h>
|
|
#include <stdbool.h>
|
|
#include <akgl/staticstring.h>
|
|
|
|
/**
|
|
* @brief Test whether two rectangles overlap, edges included.
|
|
*
|
|
* Compares the two rectangles' spans on both axes: they overlap when neither is
|
|
* wholly to one side of the other. Four comparisons, in `float32_t`, with no
|
|
* intermediate form.
|
|
*
|
|
* @param r1 First rectangle. Required.
|
|
* @param r2 Second rectangle. Required. Order does not matter.
|
|
* @param collide Receives `true` on any overlap or shared edge, `false`
|
|
* otherwise. Required.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p r1, @p r2, or @p collide is `NULL`.
|
|
*
|
|
* @note Fixed in 0.8.0. This was eight corner-containment tests, which answer a
|
|
* different question and get the cross wrong: a tall thin rectangle
|
|
* crossing a short wide one overlaps without either enclosing a corner of
|
|
* the other, and every corner test said no. It is a span comparison on
|
|
* both axes now. Two consequences for a caller upgrading: that
|
|
* arrangement starts reporting `true`, and the comparison is in `float`
|
|
* rather than through the removed akgl_Point's `int` members, so an
|
|
* overlap smaller than one pixel is no longer truncated away.
|
|
*
|
|
* @note akgl_rectangle_points, akgl_collide_point_rectangle, akgl_Point and
|
|
* akgl_RectanglePoints were removed in 0.8.0. They existed to feed the
|
|
* corner form this no longer uses, and nothing outside `tests/` called
|
|
* either function. A point-in-rectangle test is four comparisons a caller
|
|
* can write; the intermediate struct was the only thing this header was
|
|
* adding.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide);
|
|
|
|
/**
|
|
* @brief Resolve an asset path, trying the working directory before the given root.
|
|
*
|
|
* Asset files name their neighbours relatively -- a sprite definition names its
|
|
* spritesheet, a tilemap names its tilesets -- and "relative" has to mean
|
|
* relative to the file doing the naming, not to wherever the game was launched
|
|
* from. So this tries @p path against the process working directory first, and
|
|
* only if that does not exist joins it onto @p root and resolves that. Either
|
|
* way the result is absolute, with symlinks and `..` folded out.
|
|
*
|
|
* @param root Directory to fall back to, normally `dirname` of the file that
|
|
* contained @p path. Required, even when unused.
|
|
* @param path The path to resolve, relative or absolute. Required.
|
|
* @param dest Receives the resolved absolute path. Required, and must already be
|
|
* a claimed pool string -- this writes into it, it does not claim
|
|
* one for you.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p root, @p path, or @p dest is `NULL`.
|
|
* @throws AKERR_OUTOFBOUNDS If `root + "/" + path` would not fit in
|
|
* #AKGL_MAX_STRING_LENGTH.
|
|
* @throws ENOENT If neither spelling names an existing file. Any other `errno`
|
|
* `realpath(3)` can raise -- EACCES on an unsearchable directory,
|
|
* ELOOP, ENOTDIR -- propagates the same way.
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
|
*
|
|
* @note The fallback path -- the common one, since most asset references are
|
|
* relative to their own file rather than to the working directory --
|
|
* returns straight out of the ENOENT handler and so never releases the
|
|
* error context it was handling. Each such call consumes one slot of
|
|
* libakerror's fixed 128-entry context array for the life of the process.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_path_relative(char *root, char *path, akgl_String *dest);
|
|
|
|
// These are REALLY slow routines that are only useful in testing harnesses
|
|
/**
|
|
* @brief Assert that two surfaces hold byte-identical pixels.
|
|
*
|
|
* A `memcmp` over the raw pixel buffer, so it is exact: one differing byte in
|
|
* one pixel is a failure. Meant for test harnesses asserting on rendered output,
|
|
* not for anything on a frame path.
|
|
*
|
|
* @param s1 First surface. Required. Its `pitch * h` is what determines how many
|
|
* bytes are compared.
|
|
* @param s2 Second surface. Required.
|
|
* @return `NULL` when the pixels match, otherwise an error context owned by the
|
|
* caller. "Not equal" is reported as an error, not as an out-param.
|
|
* @throws AKERR_NULLPOINTER If @p s1 or @p s2 is `NULL`, or either has no
|
|
* pixel buffer.
|
|
* @throws AKERR_VALUE If the surfaces differ in size, pitch, or pixel format,
|
|
* or if their pixels differ.
|
|
*
|
|
* Dimensions, pitch and pixel format are compared first, and a difference in
|
|
* any of them is reported as a mismatch. Until 0.5.0 they were not, so a
|
|
* smaller @p s2 was read past its end instead -- benign in practice and
|
|
* immediately fatal under a memory checker.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_compare_sdl_surfaces(SDL_Surface *s1, SDL_Surface *s2);
|
|
/**
|
|
* @brief Draw two textures in turn, read the framebuffer back after each, and compare.
|
|
*
|
|
* The test-harness counterpart to akgl_compare_sdl_surfaces: it answers "do
|
|
* these two textures *render* the same", which is not the same question as "are
|
|
* these two textures identical", because the renderer's scaling and blending sit
|
|
* in between. Both are drawn into the same rectangle against a cleared target.
|
|
*
|
|
* @param t1 First texture. Required.
|
|
* @param t2 Second texture. Required.
|
|
* @param x Left edge of the region, in pixels. Used for the source
|
|
* rectangle, the destination, and the readback alike.
|
|
* @param y Top edge of the region.
|
|
* @param w Width of the region.
|
|
* @param h Height of the region.
|
|
* @param writeout Optional filename for a PNG of the *first* render, written
|
|
* under `SDL_GetBasePath()`. `NULL` skips it. This is a
|
|
* debugging aid -- when an image assertion fails, this is how
|
|
* you see what was actually drawn.
|
|
* @return `NULL` when the two renders match, otherwise an error context owned by
|
|
* the caller.
|
|
* @throws AKERR_NULLPOINTER If @p t1 or @p t2 is `NULL`.
|
|
* @throws AKGL_ERR_SDL If the framebuffer cannot be read back.
|
|
* @throws AKERR_IO If @p writeout is given and the PNG cannot be written.
|
|
* @throws AKERR_VALUE If the two renders differ.
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
|
*
|
|
* @note Until 0.5.0 both passes drew @p t1, so this always reported a match and
|
|
* every image assertion built on it asserted nothing. It draws @p t2 on
|
|
* the second pass now.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, int x, int y, int w, int h, char *writeout);
|
|
|
|
#endif // _AKGL_UTIL_H_
|