From f46c66485b758385417431d290e1a2958dececea Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 6 Sep 2025 10:50:07 -0700 Subject: Implement camera clipping for ortho maps --- demos/checkerboard/checkerboard.c | 2 +- demos/isomap/isomap.c | 6 ++--- include/isogfx/gfx2d.h | 19 +++++++++------ src/gfx2d.c | 51 +++++++++++++++++++++++++++++++++------ 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/demos/checkerboard/checkerboard.c b/demos/checkerboard/checkerboard.c index 243f7a8..f9631d8 100644 --- a/demos/checkerboard/checkerboard.c +++ b/demos/checkerboard/checkerboard.c @@ -137,7 +137,7 @@ static void render(const GfxApp* app, GfxAppState* state) { Gfx2d* iso = state->gfx; - gfx2d_render(iso); + gfx2d_render(iso, 0, 0); if ((state->xpick != -1) && (state->ypick != -1)) { gfx2d_draw_tile(iso, state->xpick, state->ypick, state->red); diff --git a/demos/isomap/isomap.c b/demos/isomap/isomap.c index 471ef57..e66d14d 100644 --- a/demos/isomap/isomap.c +++ b/demos/isomap/isomap.c @@ -47,7 +47,7 @@ static bool init(GfxApp* app, GfxAppState* state, int argc, const char** argv) { Gfx2d* iso = state->gfx; if (!gfx2d_load_map( - iso, "/home/jeanne/Nextcloud/assets/tilemaps/scrabling1.tm")) { + iso, "/home/jeanne/Nextcloud/assets/tilemaps/desert1.tm")) { return false; } @@ -100,9 +100,9 @@ static void update(GfxApp* app, GfxAppState* state, double t, double dt) { assert(state); state->camera = vec2_add(state->camera, get_camera_movement(app, (R)dt)); + gfx2d_clip_camera(state->gfx, &state->camera.x, &state->camera.y); Gfx2d* iso = state->gfx; - gfx2d_set_camera(iso, (int)state->camera.x, (int)state->camera.y); gfx2d_update(iso, t); } @@ -111,7 +111,7 @@ static void render(const GfxApp* app, GfxAppState* state) { assert(state); Gfx2d* iso = state->gfx; - gfx2d_render(iso); + gfx2d_render(iso, (int)state->camera.x, (int)state->camera.y); gfx2d_backend_render(state->backend, iso); } diff --git a/include/isogfx/gfx2d.h b/include/isogfx/gfx2d.h index a3ddbb6..33a4591 100644 --- a/include/isogfx/gfx2d.h +++ b/include/isogfx/gfx2d.h @@ -110,15 +110,8 @@ void gfx2d_set_sprite_animation(Gfx2d*, Sprite, int animation); /// Currently, this updates the sprite animations. void gfx2d_update(Gfx2d*, double t); -// TODO: Do we really need to store the camera in the library? It's not used -// for anything other than to render, so we could remove library state and -// take a camera argument in render() instead. - -/// Set the camera. -void gfx2d_set_camera(Gfx2d*, int x, int y); - /// Render the world. -void gfx2d_render(Gfx2d*); +void gfx2d_render(Gfx2d*, int camera_x, int camera_y); /// Draw/overlay a tile at position (x,y). /// @@ -127,6 +120,16 @@ void gfx2d_render(Gfx2d*); /// position (x,y) instead, use gfx2d_set_tile(). void gfx2d_draw_tile(Gfx2d*, int x, int y, Tile); +/// Clip camera coordinates to the loaded map. +/// +/// This is useful for implementing camera movement, in which you typically +/// want the camera to stay within the map's bounds. +/// +/// (x,y) are the top-left coordinates of the camera. +/// +/// A map must have previously been loaded. +void gfx2d_clip_camera(const Gfx2d*, float* x, float* y); + /// Get the virtual screen's dimensions. void gfx2d_get_screen_size(const Gfx2d*, int* width, int* height); diff --git a/src/gfx2d.c b/src/gfx2d.c index f609c98..f1f26cb 100644 --- a/src/gfx2d.c +++ b/src/gfx2d.c @@ -20,6 +20,12 @@ /// Time between animation updates. #define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS) +/// Take the maximum of two values. +#define max(a, b) ((a) > (b) ? (a) : (b)) + +/// Take the minimum of two values. +#define min(a, b) ((a) < (b) ? (a) : (b)) + typedef struct ivec2 { int x, y; } ivec2; @@ -565,7 +571,6 @@ static void draw_rect( // Rect origin can be outside screen bounds, so we must offset accordingly to // draw only the visible portion. -#define max(a, b) (a > b ? a : b) const int px_offset = max(0, -top_left.x); const int py_offset = max(0, -top_left.y); @@ -763,13 +768,11 @@ static void draw_sprites(Gfx2d* gfx) { } } -void gfx2d_set_camera(Gfx2d* gfx, int x, int y) { - assert(gfx); - gfx->camera = (ivec2){x, y}; -} - -void gfx2d_render(Gfx2d* gfx) { +void gfx2d_render(Gfx2d* gfx, int camera_x, int camera_y) { assert(gfx); + // Storing the camera mostly for debugging convenience. It could otherwise be + // passed around. + gfx->camera = (ivec2){camera_x, camera_y}; draw_map(gfx); draw_sprites(gfx); } @@ -789,6 +792,40 @@ void gfx2d_draw_tile(Gfx2d* gfx, int x, int y, Tile tile) { } } +static void clip_camera_ortho(const Gfx2d* gfx, float* x, float* y) { + assert(gfx); + assert(gfx->map); + + if (x != nullptr) { + const int width_pixels = gfx->map->world_width * gfx->map->base_tile_width; + const int x_max = width_pixels - gfx->screen.width; + *x = min((float)x_max, max(0.F, *x)); + } + if (y != nullptr) { + const int height_pixels = + gfx->map->world_height * gfx->map->base_tile_height; + const int y_max = height_pixels - gfx->screen.height; + *y = min((float)y_max, max(0.F, *y)); + } +} + +void gfx2d_clip_camera(const Gfx2d* gfx, float* x, float* y) { + assert(gfx); + assert(gfx->map); + assert(x); + assert(y); + + const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags; + switch (flags->orientation) { + case Tm_Orthogonal: + clip_camera_ortho(gfx, x, y); + break; + case Tm_Isometric: + // TODO: Clip camera in isometric maps. + break; + } +} + void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) { assert(gfx); assert(width); -- cgit v1.2.3