From 0831d5bce79008bfa6404f8e8116ae8290442fde Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 24 Jun 2023 18:46:33 -0700 Subject: Isometric Renderer initial commit. --- gfx-iso/src/isogfx.c | 361 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 gfx-iso/src/isogfx.c (limited to 'gfx-iso/src/isogfx.c') diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c new file mode 100644 index 0000000..27981f9 --- /dev/null +++ b/gfx-iso/src/isogfx.c @@ -0,0 +1,361 @@ +#include + +#include + +#include +#include +#include +#include +#include + +/// Maximum number of tiles unless the user chooses a non-zero value. +#define DEFAULT_MAX_NUM_TILES 1024 + +typedef struct TileData { + Pixel pixels[1]; // Dynamically allocated. +} TileData; + +DEF_MEMPOOL_DYN(TilePool, TileData) + +typedef struct IsoGfx { + Tile* world; + Pixel* screen; + uint8_t* tile_mask; + TilePool tiles; + int screen_width; + int screen_height; + int tile_width; + int tile_height; + int world_width; + int world_height; + int max_num_tiles; +} IsoGfx; + +typedef struct ivec2 { + int x, y; +} ivec2; + +typedef struct vec2 { + double x, y; +} vec2; + +static inline ivec2 ivec2_add(ivec2 a, ivec2 b) { + return (ivec2){.x = a.x + b.x, .y = a.y + b.y}; +} + +static inline ivec2 ivec2_scale(ivec2 a, int s) { + return (ivec2){.x = a.x * s, .y = a.y * s}; +} + +static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) { + return (ivec2){ + .x = (iso.x - iso.y) * (s / 2) + (w / 2), .y = (iso.x + iso.y) * (t / 2)}; +} + +static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { + const double one_over_s = 1. / (double)s; + const double one_over_t = 1. / (double)t; + const double x = cart.x - (double)(w / 2); + + return (vec2){ + .x = (int)(one_over_s * x + one_over_t * cart.y), + .y = (int)(-one_over_s * x + one_over_t * cart.y)}; +} + +Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { + assert(iso); + assert(tile); + assert(tile->pixels); + assert(x >= 0); + assert(y >= 0); + assert(x < iso->tile_width); + assert(y < iso->tile_height); + return &tile->pixels[y * iso->tile_width + x]; +} + +Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { + assert(iso); + assert(tile); + assert(tile->pixels); + assert(x >= 0); + assert(y >= 0); + assert(x < iso->tile_width); + assert(y < iso->tile_height); + return tile->pixels[y * iso->tile_width + x]; +} + +static inline Tile world_xy(IsoGfx* iso, int x, int y) { + assert(iso); + assert(x >= 0); + assert(y >= 0); + assert(x < iso->world_width); + assert(y < iso->world_height); + return iso->world[y * iso->world_width + x]; +} + +static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { + assert(iso); + assert(x >= 0); + assert(y >= 0); + assert(x < iso->world_width); + assert(y < iso->world_height); + return &iso->world[y * iso->world_width + x]; +} + +static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { + assert(iso); + assert(x >= 0); + assert(y >= 0); + assert(x < iso->screen_width); + assert(y < iso->screen_height); + return iso->screen[y * iso->screen_width + x]; +} + +static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { + assert(iso); + assert(x >= 0); + assert(y >= 0); + assert(x < iso->screen_width); + assert(y < iso->screen_height); + return &iso->screen[y * iso->screen_width + x]; +} + +static void draw_tile(IsoGfx* iso, ivec2 so, Tile tile) { + assert(iso); + + const TileData* data = mempool_get_block(&iso->tiles, tile); + assert(data); + + for (int py = 0; py < iso->tile_height; ++py) { + for (int px = 0; px < iso->tile_width; ++px) { + const Pixel colour = tile_xy(iso, data, px, py); + const int sx = so.x + px; + const int sy = so.y + py; + if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) && + (sy < iso->screen_height)) { + const uint8_t mask = iso->tile_mask[py * iso->tile_width + px]; + if (mask == 1) { + *screen_xy_mut(iso, sx, sy) = colour; + } + } + } + } +} + +static void draw(IsoGfx* iso) { + assert(iso); + + const int W = iso->screen_width; + const int H = iso->screen_height; + + memset(iso->screen, 0, W * H * sizeof(Pixel)); + + const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; + const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; + const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; + + // TODO: Since the world will generally be larger than the screen, it + // would be best to walk in screen space and fetch the tile. + // The tile-centric approach might be more cache-friendly, however, since the + // screen-centric approach would juggle multiple tiles throughout the scan. + for (int ty = 0; ty < iso->world_height; ++ty) { + for (int tx = 0; tx < iso->world_width; ++tx) { + const Tile tile = world_xy(iso, tx, ty); + const ivec2 so = + ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty))); + draw_tile(iso, so, tile); + } + } +} + +/// Creates a tile mask procedurally. +static void make_tile_mask(IsoGfx* iso) { + assert(iso); + assert(iso->tile_mask); + + for (int y = 0; y < iso->tile_height / 2; ++y) { + const int mask_start = iso->tile_width / 2 - 2 * y - 1; + const int mask_end = iso->tile_width / 2 + 2 * y + 1; + for (int x = 0; x < iso->tile_width; ++x) { + const bool masked = (mask_start <= x) && (x <= mask_end); + const uint8_t val = masked ? 1 : 0; + + // Top half. + iso->tile_mask[y * iso->tile_width + x] = val; + + // Bottom half reflects the top half. + const int y_reflected = iso->tile_height - y - 1; + iso->tile_mask[y_reflected * iso->tile_width + x] = val; + } + } +} + +/// Creates a tile with a constant colour. +static void make_tile_from_colour( + const IsoGfx* iso, Pixel colour, TileData* tile) { + assert(iso); + assert(tile); + + for (int y = 0; y < iso->tile_height; ++y) { + for (int x = 0; x < iso->tile_width; ++x) { + *tile_xy_mut(iso, tile, x, y) = colour; + } + } +} + +IsoGfx* isogfx_new(const IsoGfxDesc* desc) { + assert(desc->screen_width > 0); + assert(desc->screen_height > 0); + assert(desc->tile_width > 0); + assert(desc->tile_height > 0); + // Part of our implementation assumes even widths and heights for greater + // precision. + assert((desc->screen_width & 1) == 0); + assert((desc->screen_height & 1) == 0); + assert((desc->tile_width & 1) == 0); + assert((desc->tile_height & 1) == 0); + + IsoGfx* iso = calloc(1, sizeof(IsoGfx)); + if (!iso) { + return 0; + } + + iso->screen_width = desc->screen_width; + iso->screen_height = desc->screen_height; + iso->tile_width = desc->tile_width; + iso->tile_height = desc->tile_height; + iso->world_width = desc->world_width; + iso->world_height = desc->world_height; + iso->max_num_tiles = + desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES; + + const int world_size = desc->world_width * desc->world_height; + const int screen_size = desc->screen_width * desc->screen_height; + const int tile_size = desc->tile_width * desc->tile_height; + + const int tile_size_bytes = tile_size * (int)sizeof(Pixel); + + if (!(iso->world = calloc(world_size, sizeof(Tile)))) { + goto cleanup; + } + if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { + goto cleanup; + } + if (!(iso->tile_mask = calloc(tile_size, sizeof(uint8_t)))) { + goto cleanup; + } + if (!mempool_make_dyn(&iso->tiles, iso->max_num_tiles, tile_size_bytes)) { + goto cleanup; + } + + make_tile_mask(iso); + + return iso; + +cleanup: + isogfx_del(&iso); + return 0; +} + +void isogfx_del(IsoGfx** pIso) { + assert(pIso); + IsoGfx* iso = *pIso; + if (iso) { + if (iso->world) { + free(iso->world); + } + if (iso->screen) { + free(iso->screen); + } + if (iso->tile_mask) { + free(iso->tile_mask); + } + mempool_del(&iso->tiles); + free(iso); + } +} + +Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { + assert(iso); + assert(desc); + + TileData* tile = mempool_alloc(&iso->tiles); + assert(tile); // TODO: Make this a hard assert. + + switch (desc->type) { + case TileFromColour: + make_tile_from_colour(iso, desc->colour, tile); + break; + case TileFromFile: + assert(false); // TODO + break; + case TileFromMemory: + assert(false); // TODO + break; + } + + return (Tile)mempool_get_block_index(&iso->tiles, tile); +} + +void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { + assert(iso); + *world_xy_mut(iso, x, y) = tile; +} + +void isogfx_pick_tile( + const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { + assert(iso); + assert(xiso); + assert(yiso); + + const vec2 xy_iso = cart2iso( + (vec2){.x = xcart, .y = ycart}, iso->tile_width, iso->tile_height, + iso->screen_width); + + const int x = (int)xy_iso.x; + const int y = (int)xy_iso.y; + + if ((0 <= x) && (x < iso->world_width) && (0 <= y) && + (y < iso->world_height)) { + *xiso = x; + *yiso = y; + } else { + *xiso = -1; + } +} + +void isogfx_render(IsoGfx* iso) { + assert(iso); + draw(iso); +} + +void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) { + assert(iso); + assert(x >= 0); + assert(y >= 0); + assert(x < iso->world_width); + assert(y < iso->world_height); + + const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; + const ivec2 vx = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; + const ivec2 vy = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; + const ivec2 so = + ivec2_add(o, ivec2_add(ivec2_scale(vx, x), ivec2_scale(vy, y))); + + draw_tile(iso, so, tile); +} + +const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) { + assert(iso); + return iso->screen; +} + +int isogfx_world_width(const IsoGfx* iso) { + assert(iso); + return iso->world_width; +} + +int isogfx_world_height(const IsoGfx* iso) { + assert(iso); + return iso->world_height; +} -- cgit v1.2.3