From bd57f345ed9dbed1d81683e48199626de2ea9044 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 27 Jun 2025 10:18:39 -0700 Subject: Restructure project --- gfx-iso/src/backend.c | 199 ----------- gfx-iso/src/isogfx.c | 952 -------------------------------------------------- 2 files changed, 1151 deletions(-) delete mode 100644 gfx-iso/src/backend.c delete mode 100644 gfx-iso/src/isogfx.c (limited to 'gfx-iso/src') diff --git a/gfx-iso/src/backend.c b/gfx-iso/src/backend.c deleted file mode 100644 index db91647..0000000 --- a/gfx-iso/src/backend.c +++ /dev/null @@ -1,199 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -typedef struct IsoBackend { - Gfx* gfx; - Scene* scene; - /// The screen or "iso screen" refers to the colour buffer of the iso graphics - /// library. This texture is used to draw the iso screen onto the graphics - /// window. - Texture* screen_texture; - /// Window size. - int window_width; - int window_height; - /// The viewport refers to the area inside the window to which screen_texture - /// is drawn. It is a scaled version of the iso screen, scaled while - /// respecting the iso screen's aspect ratio to prevent distortion. - int viewport_x, viewport_y, viewport_width, viewport_height; - double stretch; // Stretch factor from iso screen dimensions to viewport - // dimensions. -} IsoBackend; - -IsoBackend* IsoBackendInit(const IsoGfx* iso) { - assert(iso); - - IsoBackend* backend = calloc(1, sizeof(IsoBackend)); - if (!backend) { - return 0; - } - - if (!(backend->gfx = gfx_init())) { - goto cleanup; - } - GfxCore* gfxcore = gfx_get_core(backend->gfx); - - int screen_width, screen_height; - isogfx_get_screen_size(iso, &screen_width, &screen_height); - - if (!(backend->screen_texture = gfx_make_texture( - gfxcore, &(TextureDesc){ - .width = screen_width, - .height = screen_height, - .dimension = Texture2D, - .format = TextureSRGBA8, - .filtering = NearestFiltering, - .wrap = ClampToEdge, - .mipmaps = false}))) { - goto cleanup; - } - - ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore); - if (!shader) { - goto cleanup; - } - - Geometry* geometry = gfx_make_quad_11(gfxcore); - if (!geometry) { - goto cleanup; - } - - MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1}; - material_desc.uniforms[0] = (ShaderUniform){ - .type = UniformTexture, - .value.texture = backend->screen_texture, - .name = sstring_make("Texture")}; - Material* material = gfx_make_material(&material_desc); - if (!material) { - return false; - } - - const MeshDesc mesh_desc = - (MeshDesc){.geometry = geometry, .material = material, .shader = shader}; - Mesh* mesh = gfx_make_mesh(&mesh_desc); - if (!mesh) { - goto cleanup; - } - - SceneObject* object = - gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}}); - if (!object) { - goto cleanup; - } - - backend->scene = gfx_make_scene(); - SceneNode* node = gfx_make_object_node(object); - SceneNode* root = gfx_get_scene_root(backend->scene); - gfx_set_node_parent(node, root); - - return backend; - -cleanup: - if (backend->gfx) { - gfx_destroy(&backend->gfx); - } - free(backend); - return 0; -} - -void IsoBackendShutdown(IsoBackend** ppApp) { - assert(ppApp); - - IsoBackend* app = *ppApp; - if (!app) { - return; - } - - gfx_destroy(&app->gfx); -} - -void IsoBackendResizeWindow( - IsoBackend* app, const IsoGfx* iso, int width, int height) { - assert(app); - assert(iso); - - app->window_width = width; - app->window_height = height; - - // Virtual screen dimensions. - int screen_width, screen_height; - isogfx_get_screen_size(iso, &screen_width, &screen_height); - - // Stretch the virtual screen onto the viewport while respecting the screen's - // aspect ratio to prevent distortion. - if (width > height) { // Wide screen. - app->stretch = (double)height / (double)screen_height; - app->viewport_width = (int)((double)screen_width * app->stretch); - app->viewport_height = height; - app->viewport_x = (width - app->viewport_width) / 2; - app->viewport_y = 0; - } else { // Tall screen. - app->stretch = (double)width / (double)screen_width; - app->viewport_width = width; - app->viewport_height = (int)((float)screen_height * app->stretch); - app->viewport_x = 0; - app->viewport_y = (height - app->viewport_height) / 2; - } -} - -void IsoBackendRender(const IsoBackend* app, const IsoGfx* iso) { - assert(app); - assert(iso); - - const Pixel* screen = isogfx_get_screen_buffer(iso); - assert(screen); - gfx_update_texture(app->screen_texture, &(TextureDataDesc){.pixels = screen}); - - GfxCore* gfxcore = gfx_get_core(app->gfx); - Renderer* renderer = gfx_get_renderer(app->gfx); - - // Clear the whole window. - gfx_set_viewport(gfxcore, 0, 0, app->window_width, app->window_height); - gfx_clear(gfxcore, vec4_make(0, 0, 0, 0)); - - // Draw to the subregion where the virtual screen can stretch without - // distortion. - gfx_set_viewport( - gfxcore, app->viewport_x, app->viewport_y, app->viewport_width, - app->viewport_height); - - // Render the iso screen. - gfx_start_frame(gfxcore); - gfx_render_scene( - renderer, &(RenderSceneParams){ - .mode = RenderDefault, .scene = app->scene, .camera = 0}); - gfx_end_frame(gfxcore); -} - -bool IsoBackendGetMousePosition( - const IsoBackend* app, double window_x, double window_y, double* x, - double* y) { - assert(app); - - // Translate from window coordinates to the subregion where the stretched - // iso screen is rendered. - const double screen_x = window_x - app->viewport_x; - const double screen_y = window_y - app->viewport_y; - - // Position may be out of bounds. - if ((0 <= screen_x) && (screen_x < app->viewport_width) && (0 <= screen_y) && - (screen_y < app->viewport_height)) { - // Scale back from the stretched subregion to the iso screen dimensions. - *x = screen_x / app->stretch; - *y = screen_y / app->stretch; - return true; - } else { - *x = -1; - *y = -1; - return false; - } -} diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c deleted file mode 100644 index 52c4ae2..0000000 --- a/gfx-iso/src/isogfx.c +++ /dev/null @@ -1,952 +0,0 @@ -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -/// Maximum number of tiles unless the user specifies a value. -#define DEFAULT_MAX_NUM_TILES 1024 - -/// Maximum number of sprites unless the user specifies a value. -#define DEFAULT_MAX_NUM_SPRITES 128 - -/// Size of sprite sheet pool in bytes unless the user specifies a value. -#define DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES (8 * 1024 * 1024) - -/// Default animation speed. -#define ANIMATION_FPS 10 - -/// Time between animation updates. -#define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS) - -typedef struct ivec2 { - int x, y; -} ivec2; - -typedef struct vec2 { - double x, y; -} vec2; - -// ----------------------------------------------------------------------------- -// Tile set (TS) and tile map (TM) file formats. -// ----------------------------------------------------------------------------- - -/// Maximum length of path strings in .TS and .TM files. -#define MAX_PATH_LENGTH 128 - -typedef struct Ts_Tile { - uint16_t width; /// Tile width in pixels. - uint16_t height; /// Tile height in pixels. - Pixel pixels[1]; /// Count: width * height. -} Ts_Tile; - -typedef struct Ts_TileSet { - uint16_t num_tiles; - uint16_t max_tile_width; /// Maximum tile width in pixels. - uint16_t max_tile_height; /// Maximum tile height in pixels. - Ts_Tile tiles[1]; /// Count: num_tiles. -} Ts_TileSet; - -typedef struct Tm_Layer { - union { - char tileset_path[MAX_PATH_LENGTH]; // Relative to the Tm_Map file. - }; - Tile tiles[1]; /// Count: world_width * world_height. -} Tm_Layer; - -typedef struct Tm_Map { - uint16_t world_width; /// World width in number of tiles. - uint16_t world_height; /// World height in number of tiles. - uint16_t base_tile_width; - uint16_t base_tile_height; - uint16_t num_layers; - Tm_Layer layers[1]; // Count: num_layers. -} Tm_Map; - -static inline const Tm_Layer* tm_map_get_next_layer( - const Tm_Map* map, const Tm_Layer* layer) { - assert(map); - assert(layer); - return (const Tm_Layer*)((const uint8_t*)layer + sizeof(Tm_Layer) + - ((map->world_width * map->world_height - 1) * - sizeof(Tile))); -} - -static inline const Ts_Tile* ts_tileset_get_next_tile( - const Ts_TileSet* tileset, const Ts_Tile* tile) { - assert(tileset); - assert(tile); - return (const Ts_Tile*)((const uint8_t*)tile + sizeof(Ts_Tile) + - ((tile->width * tile->height - 1) * sizeof(Pixel))); -} - -// ----------------------------------------------------------------------------- -// Sprite sheet file format. -// ----------------------------------------------------------------------------- - -/// A row of sprites in a sprite sheet. -/// -/// Each row in a sprite sheet can have a different number of columns. -/// -/// The pixels of the row follow a "sprite-major" order. It contains the -/// 'sprite_width * sprite_height' pixels for the first column/sprite, then the -/// second column/sprite, etc. -/// -/// Pixels are 8-bit indices into the sprite sheet's colour palette. -typedef struct Ss_Row { - uint16_t num_cols; /// Number of columns in this row. - uint8_t pixels[1]; /// Count: num_cols * sprite_width * sprite_height. -} Ss_Row; - -typedef struct Ss_Palette { - uint16_t num_colours; - Pixel colours[1]; /// Count: num_colors. -} Ss_Palette; - -/// Sprite sheet top-level data definition. -/// -/// Sprite width and height are assumed constant throughout the sprite sheet. -typedef struct Ss_SpriteSheet { - uint16_t sprite_width; /// Sprite width in pixels. - uint16_t sprite_height; /// Sprite height in pixels. - uint16_t num_rows; - Ss_Palette palette; /// Variable size. - Ss_Row rows[1]; /// Count: num_rows. Variable offset. -} Ss_SpriteSheet; - -static inline const Ss_Row* get_sprite_sheet_row( - const Ss_SpriteSheet* sheet, int row) { - assert(sheet); - assert(row >= 0); - assert(row < sheet->num_rows); - // Skip over the palette. - const Ss_Row* rows = - (const Ss_Row*)(&sheet->palette.colours[0] + sheet->palette.num_colours); - return &rows[row]; -} - -static inline const uint8_t* get_sprite_sheet_sprite( - const Ss_SpriteSheet* sheet, const Ss_Row* row, int col) { - assert(sheet); - assert(row); - assert(col >= 0); - assert(col < row->num_cols); - const int sprite_offset = col * sheet->sprite_width * sheet->sprite_height; - const uint8_t* sprite = &row->pixels[sprite_offset]; - return sprite; -} - -// ----------------------------------------------------------------------------- -// Renderer state. -// ----------------------------------------------------------------------------- - -typedef struct TileData { - uint16_t width; - uint16_t height; - uint16_t pixels_handle; // Handle to the tile's pixels in the pixel pool. -} TileData; - -// File format is already convenient for working in memory. -typedef Ss_Row SpriteSheetRow; -typedef Ss_SpriteSheet SpriteSheetData; - -typedef struct SpriteData { - SpriteSheet sheet; // Handle to the sprite's sheet. - ivec2 position; - int animation; // Current animation. - int frame; // Current frame of animation. -} SpriteData; - -DEF_MEMPOOL_DYN(TilePool, TileData) -DEF_MEM_DYN(PixelPool, Pixel) - -DEF_MEMPOOL_DYN(SpritePool, SpriteData) -DEF_MEM_DYN(SpriteSheetPool, SpriteSheetData) - -typedef struct IsoGfx { - int screen_width; - int screen_height; - int tile_width; - int tile_height; - int world_width; - int world_height; - int max_num_sprites; - int sprite_sheet_pool_size_bytes; - double last_animation_time; - Tile* world; - Pixel* screen; - TilePool tiles; - PixelPool pixels; - SpritePool sprites; - SpriteSheetPool sheets; -} IsoGfx; - -// ----------------------------------------------------------------------------- -// Math and world / tile / screen access. -// ----------------------------------------------------------------------------- - -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)}; -} - -// Method 1. -// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { -// const double x = cart.x - (double)(w / 2); -// const double xiso = (x * t + cart.y * s) / (double)(s * t); -// return (vec2){ -// .x = (int)(xiso), .y = (int)((2.0 / (double)t) * cart.y - xiso)}; -//} - -// Method 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 = (one_over_s * x + one_over_t * cart.y), - .y = (-one_over_s * x + one_over_t * cart.y)}; -} - -static const Pixel* tile_xy_const_ref( - const IsoGfx* iso, const TileData* tile, int x, int y) { - assert(iso); - assert(tile); - assert(x >= 0); - assert(y >= 0); - assert(x < tile->width); - assert(y < tile->height); - return &mem_get_chunk(&iso->pixels, tile->pixels_handle)[y * tile->width + x]; -} - -// static Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { -// return *tile_xy_const_ref(iso, tile, x, y); -// } - -static Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { - return (Pixel*)tile_xy_const_ref(iso, tile, x, y); -} - -static inline const Tile* world_xy_const_ref(const 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(const IsoGfx* iso, int x, int y) { - return *world_xy_const_ref(iso, x, y); -} - -static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { - return (Tile*)world_xy_const_ref(iso, x, y); -} - -static inline const Pixel* screen_xy_const_ref( - const 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(IsoGfx* iso, int x, int y) { - return *screen_xy_const_ref(iso, x, y); -} - -static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { - return (Pixel*)screen_xy_const_ref(iso, x, y); -} - -static int calc_num_tile_blocks( - int base_tile_width, int base_tile_height, int tile_width, - int tile_height) { - const int base_tile_size = base_tile_width * base_tile_height; - const int tile_size = tile_width * tile_height; - const int num_blocks = tile_size / base_tile_size; - return num_blocks; -} - -// ----------------------------------------------------------------------------- -// Renderer, world and tile management. -// ----------------------------------------------------------------------------- - -IsoGfx* isogfx_new(const IsoGfxDesc* desc) { - assert(desc->screen_width > 0); - assert(desc->screen_height > 0); - // Part of our implementation assumes even widths and heights for precision. - assert((desc->screen_width & 1) == 0); - assert((desc->screen_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->last_animation_time = 0.0; - - iso->max_num_sprites = desc->max_num_sprites == 0 ? DEFAULT_MAX_NUM_SPRITES - : desc->max_num_sprites; - iso->sprite_sheet_pool_size_bytes = desc->sprite_sheet_pool_size_bytes == 0 - ? DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES - : desc->sprite_sheet_pool_size_bytes; - - const int screen_size = desc->screen_width * desc->screen_height; - if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { - goto cleanup; - } - - return iso; - -cleanup: - isogfx_del(&iso); - return 0; -} - -/// Destroy the world, its tile set, and the underlying pools. -static void destroy_world(IsoGfx* iso) { - assert(iso); - if (iso->world) { - free(iso->world); - iso->world = 0; - } - mempool_del(&iso->tiles); - mem_del(&iso->pixels); -} - -/// Destroy all loaded sprites and the underlying pools. -static void destroy_sprites(IsoGfx* iso) { - assert(iso); - mempool_del(&iso->sprites); - mem_del(&iso->sheets); -} - -void isogfx_del(IsoGfx** pIso) { - assert(pIso); - IsoGfx* iso = *pIso; - if (iso) { - destroy_world(iso); - destroy_sprites(iso); - if (iso->screen) { - free(iso->screen); - iso->screen = 0; - } - free(iso); - *pIso = 0; - } -} - -bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { - assert(iso); - assert(desc); - assert(desc->tile_width > 0); - assert(desc->tile_height > 0); - // Part of our implementation assumes even widths and heights for greater - // precision. - assert((desc->tile_width & 1) == 0); - assert((desc->tile_height & 1) == 0); - - // Handle recreation by destroying the previous world. - destroy_world(iso); - - iso->tile_width = desc->tile_width; - iso->tile_height = desc->tile_height; - iso->world_width = desc->world_width; - iso->world_height = desc->world_height; - - const int world_size = desc->world_width * desc->world_height; - const int tile_size = desc->tile_width * desc->tile_height; - const int tile_size_bytes = tile_size * (int)sizeof(Pixel); - const int tile_pool_size = - desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES; - - if (!(iso->world = calloc(world_size, sizeof(Tile)))) { - goto cleanup; - } - if (!mempool_make_dyn(&iso->tiles, world_size, sizeof(TileData))) { - goto cleanup; - } - if (!mem_make_dyn(&iso->pixels, tile_pool_size, tile_size_bytes)) { - goto cleanup; - } - - return true; - -cleanup: - destroy_world(iso); - return false; -} - -bool isogfx_load_world(IsoGfx* iso, const char* filepath) { - assert(iso); - assert(filepath); - - bool success = false; - - // Handle recreation by destroying the previous world. - destroy_world(iso); - - // Load the map. - printf("Load tile map: %s\n", filepath); - Tm_Map* map = read_file(filepath); - if (!map) { - goto cleanup; - } - - // Allocate memory for the map and tile sets. - const int world_size = map->world_width * map->world_height; - const int base_tile_size = map->base_tile_width * map->base_tile_height; - const int base_tile_size_bytes = base_tile_size * (int)sizeof(Pixel); - // TODO: Need to get the total number of tiles from the map. - const int tile_pool_size = DEFAULT_MAX_NUM_TILES; - - if (!(iso->world = calloc(world_size, sizeof(Tile)))) { - goto cleanup; - } - if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) { - goto cleanup; - } - if (!mem_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) { - goto cleanup; - } - - // Load the tile sets. - const Tm_Layer* layer = &map->layers[0]; - // TODO: Handle num_layers layers. - for (int i = 0; i < 1; ++i) { - const char* ts_path = layer->tileset_path; - - // Tile set path is relative to the tile map file. Make it relative to the - // current working directory before loading. - char ts_path_cwd[PATH_MAX] = {0}; - if (!path_make_relative(filepath, ts_path, ts_path_cwd, PATH_MAX)) { - goto cleanup; - } - - Ts_TileSet* tileset = read_file(ts_path_cwd); - if (!tileset) { - goto cleanup; - }; - - // Load tile data. - const Ts_Tile* tile = &tileset->tiles[0]; - for (uint16_t j = 0; j < tileset->num_tiles; ++j) { - // Tile dimensions should be a multiple of the base tile size. - assert((tile->width % map->base_tile_width) == 0); - assert((tile->height % map->base_tile_height) == 0); - - // Allocate N base tile size blocks for the tile. - const uint16_t tile_size = tile->width * tile->height; - const int num_blocks = tile_size / base_tile_size; - Pixel* pixels = mem_alloc(&iso->pixels, num_blocks); - assert(pixels); - memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel)); - - // Allocate the tile data. - TileData* tile_data = mempool_alloc(&iso->tiles); - assert(tile_data); - tile_data->width = tile->width; - tile_data->height = tile->height; - tile_data->pixels_handle = - (uint16_t)mem_get_chunk_handle(&iso->pixels, pixels); - - tile = ts_tileset_get_next_tile(tileset, tile); - } - - printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd); - - free(tileset); - layer = tm_map_get_next_layer(map, layer); - } - - // Load the map into the world. - layer = &map->layers[0]; - // TODO: Handle num_layers layers. - for (int i = 0; i < 1; ++i) { - memcpy(iso->world, layer->tiles, world_size * sizeof(Tile)); - - // TODO: We need to handle 'firsgid' in TMX files. - for (int j = 0; j < world_size; ++j) { - iso->world[j] -= 1; - } - - layer = tm_map_get_next_layer(map, layer); - } - - iso->world_width = map->world_width; - iso->world_height = map->world_height; - iso->tile_width = map->base_tile_width; - iso->tile_height = map->base_tile_height; - - success = true; - -cleanup: - if (map) { - free(map); - } - if (!success) { - destroy_world(iso); - } - return success; -} - -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; -} - -/// Create a tile mask procedurally. -static void make_tile_from_colour( - const IsoGfx* iso, Pixel colour, TileData* tile) { - assert(iso); - assert(tile); - - const int width = tile->width; - const int height = tile->height; - const int r = width / height; - - for (int y = 0; y < height / 2; ++y) { - const int mask_start = width / 2 - r * y - 1; - const int mask_end = width / 2 + r * y + 1; - for (int x = 0; x < width; ++x) { - const bool mask = (mask_start <= x) && (x <= mask_end); - const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; - - // Top half. - *tile_xy_mut(iso, tile, x, y) = val; - - // Bottom half reflects the top half. - const int y_reflected = height - y - 1; - *tile_xy_mut(iso, tile, x, y_reflected) = val; - } - } -} - -Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { - assert(iso); - assert(desc); - // Client must create world before creating tiles. - assert(iso->tile_width > 0); - assert(iso->tile_height > 0); - - TileData* tile = mempool_alloc(&iso->tiles); - assert(tile); // TODO: Make this a hard assert. - - const int num_blocks = calc_num_tile_blocks( - iso->tile_width, iso->tile_height, desc->width, desc->height); - - Pixel* pixels = mem_alloc(&iso->pixels, num_blocks); - assert(pixels); // TODO: Make this a hard assert. - - tile->width = desc->width; - tile->height = desc->height; - tile->pixels_handle = mem_get_chunk_handle(&iso->pixels, pixels); - - 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_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { - assert(iso); - for (int y = y0; y < y1; ++y) { - for (int x = x0; x < x1; ++x) { - isogfx_set_tile(iso, x, y, tile); - } - } -} - -bool isogfx_load_sprite_sheet( - IsoGfx* iso, const char* filepath, SpriteSheet* p_sheet) { - assert(iso); - assert(filepath); - assert(p_sheet); - - bool success = false; - - // Lazy initialization of sprite pools. - if (mempool_capacity(&iso->sprites) == 0) { - if (!mempool_make_dyn( - &iso->sprites, iso->max_num_sprites, sizeof(SpriteData))) { - return false; - } - } - if (mem_capacity(&iso->sheets) == 0) { - // Using a block size of 1 byte for sprite sheet data. - if (!mem_make_dyn(&iso->sheets, iso->sprite_sheet_pool_size_bytes, 1)) { - return false; - } - } - - // Load sprite sheet file. - printf("Load sprite sheet: %s\n", filepath); - FILE* file = fopen(filepath, "rb"); - if (file == NULL) { - goto cleanup; - } - const size_t sheet_size = get_file_size(file); - SpriteSheetData* ss_sheet = mem_alloc(&iso->sheets, sheet_size); - if (!ss_sheet) { - goto cleanup; - } - if (fread(ss_sheet, sheet_size, 1, file) != 1) { - goto cleanup; - } - - *p_sheet = mem_get_chunk_handle(&iso->sheets, ss_sheet); - success = true; - -cleanup: - // Pools remain initialized since client may attempt to load other sprites. - if (file != NULL) { - fclose(file); - } - if (!success) { - if (ss_sheet) { - mem_free(&iso->sheets, &ss_sheet); - } - } - return success; -} - -Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) { - assert(iso); - - SpriteData* sprite = mempool_alloc(&iso->sprites); - assert(sprite); - - sprite->sheet = sheet; - - return mempool_get_block_index(&iso->sprites, sprite); -} - -#define with_sprite(SPRITE, BODY) \ - { \ - SpriteData* data = mempool_get_block(&iso->sprites, sprite); \ - assert(data); \ - BODY; \ - } - -void isogfx_set_sprite_position(IsoGfx* iso, Sprite sprite, int x, int y) { - assert(iso); - with_sprite(sprite, { - data->position.x = x; - data->position.y = y; - }); -} - -void isogfx_set_sprite_animation(IsoGfx* iso, Sprite sprite, int animation) { - assert(iso); - with_sprite(sprite, { data->animation = animation; }); -} - -void isogfx_update(IsoGfx* iso, double t) { - assert(iso); - - // If this is the first time update() is called after initialization, just - // record the starting animation time. - if (iso->last_animation_time == 0.0) { - iso->last_animation_time = t; - return; - } - - if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) { - // TODO: Consider linking animated sprites in a list so that we only walk - // over those here and not also the static sprites. - mempool_foreach(&iso->sprites, sprite, { - const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet); - assert(sheet); // TODO: Make this a hard assert inside the mem/pool. - const SpriteSheetRow* row = - get_sprite_sheet_row(sheet, sprite->animation); - sprite->frame = (sprite->frame + 1) % row->num_cols; - }); - - iso->last_animation_time = t; - } -} - -// ----------------------------------------------------------------------------- -// Rendering and picking. -// ----------------------------------------------------------------------------- - -typedef struct CoordSystem { - ivec2 o; /// Origin. - ivec2 x; - ivec2 y; -} CoordSystem; - -/// Create the basis for the isometric coordinate system with origin and vectors -/// expressed in the Cartesian system. -static CoordSystem make_iso_coord_system(const IsoGfx* iso) { - assert(iso); - const ivec2 o = {iso->screen_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}; - return (CoordSystem){o, x, y}; -} - -/// Get the screen position of the top diamond-corner of the tile at world -/// (x,y). -static ivec2 GetTileScreenOrigin( - const CoordSystem iso_space, int world_x, int world_y) { - const ivec2 vx_offset = ivec2_scale(iso_space.x, world_x); - const ivec2 vy_offset = ivec2_scale(iso_space.y, world_y); - const ivec2 screen_origin = - ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset)); - - return screen_origin; -} - -static Pixel alpha_blend(Pixel src, Pixel dst) { - if ((src.a == 255) || (dst.a == 0)) { - return src; - } - const uint16_t one_minus_alpha = 255 - src.a; -#define blend(s, d) \ - (Channel)( \ - (double)((uint16_t)s * (uint16_t)src.a + \ - (uint16_t)d * one_minus_alpha) / \ - 255.0) - return (Pixel){ - .r = blend(src.r, dst.r), - .g = blend(src.g, dst.g), - .b = blend(src.b, dst.b), - .a = src.a}; -} - -/// Draw a rectangle (tile or sprite). -/// -/// The rectangle's top-left corner is mapped to the screen space position given -/// by 'top_left'. -/// -/// The rectangle's pixels are assumed to be arranged in a linear, row-major -/// fashion. -/// -/// If indices are given, then the image is assumed to be colour-paletted, where -/// 'pixels' is the palette and 'indices' the pixel indices. Otherwise, the -/// image is assumed to be in plain RGBA format. -static void draw_rect( - IsoGfx* iso, ivec2 top_left, int rect_width, int rect_height, - const Pixel* pixels, const uint8_t* indices) { - assert(iso); - -#define rect_pixel(X, Y) \ - (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X]) - - // 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); - - // Rect can exceed screen bounds, so clip along Y and X as we draw. - for (int py = py_offset; - (py < rect_height) && (top_left.y + py < iso->screen_height); ++py) { - const int sy = top_left.y + py; - for (int px = px_offset; - (px < rect_width) && (top_left.x + px < iso->screen_width); ++px) { - const Pixel colour = rect_pixel(px, py); - if (colour.a > 0) { - const int sx = top_left.x + px; - const Pixel dst = screen_xy(iso, sx, sy); - const Pixel final = alpha_blend(colour, dst); - *screen_xy_mut(iso, sx, sy) = final; - } - } - } -} - -/// Draw a tile. -/// -/// 'screen_origin' is the screen coordinates of the top diamond-corner of the -/// tile (the base tile for super tiles). -/// World (0, 0) -> (screen_width / 2, 0). -static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) { - assert(iso); - - const TileData* tile_data = mempool_get_block(&iso->tiles, tile); - assert(tile_data); - const Pixel* pixels = tile_xy_const_ref(iso, tile_data, 0, 0); - - // Move from the top diamond-corner to the top-left corner of the tile image. - // For regular tiles, tile height == base tile height, so the y offset is 0. - // For super tiles, move as high up as the height of the tile. - const ivec2 offset = { - -(iso->tile_width / 2), tile_data->height - iso->tile_height}; - const ivec2 top_left = ivec2_add(screen_origin, offset); - - draw_rect(iso, top_left, tile_data->width, tile_data->height, pixels, 0); -} - -static void draw_world(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 CoordSystem iso_space = make_iso_coord_system(iso); - - // TODO: Culling. - // Ex: map the screen corners to tile space to cull. - // Ex: walk in screen space and fetch the tile. - // The tile-centric approach might be more cache-friendly since the - // screen-centric approach would juggle multiple tiles throughout the scan. - for (int wy = 0; wy < iso->world_height; ++wy) { - for (int wx = 0; wx < iso->world_width; ++wx) { - const Tile tile = world_xy(iso, wx, wy); - const ivec2 screen_origin = GetTileScreenOrigin(iso_space, wx, wy); - draw_tile(iso, screen_origin, tile); - } - } -} - -static void draw_sprite( - IsoGfx* iso, ivec2 origin, const SpriteData* sprite, - const SpriteSheetData* sheet) { - assert(iso); - assert(sprite); - assert(sheet); - assert(sprite->animation >= 0); - assert(sprite->animation < sheet->num_rows); - assert(sprite->frame >= 0); - - const SpriteSheetRow* row = get_sprite_sheet_row(sheet, sprite->animation); - const uint8_t* frame = get_sprite_sheet_sprite(sheet, row, sprite->frame); - draw_rect( - iso, origin, sheet->sprite_width, sheet->sprite_height, - sheet->palette.colours, frame); -} - -static void draw_sprites(IsoGfx* iso) { - assert(iso); - - const CoordSystem iso_space = make_iso_coord_system(iso); - - mempool_foreach(&iso->sprites, sprite, { - const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet); - assert(sheet); - - const ivec2 screen_origin = - GetTileScreenOrigin(iso_space, sprite->position.x, sprite->position.y); - draw_sprite(iso, screen_origin, sprite, sheet); - }); -} - -void isogfx_render(IsoGfx* iso) { - assert(iso); - draw_world(iso); - draw_sprites(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 CoordSystem iso_space = make_iso_coord_system(iso); - const ivec2 screen_origin = GetTileScreenOrigin(iso_space, x, y); - draw_tile(iso, screen_origin, tile); -} - -bool isogfx_resize(IsoGfx* iso, int screen_width, int screen_height) { - assert(iso); - assert(iso->screen); - - const int current_size = iso->screen_width * iso->screen_height; - const int new_size = screen_width * screen_height; - - if (new_size > current_size) { - Pixel* new_screen = calloc(new_size, sizeof(Pixel)); - if (new_screen) { - free(iso->screen); - iso->screen = new_screen; - } else { - return false; - } - } - iso->screen_width = screen_width; - iso->screen_height = screen_height; - return true; -} - -void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) { - assert(iso); - assert(width); - assert(height); - *width = iso->screen_width; - *height = iso->screen_height; -} - -const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) { - assert(iso); - return iso->screen; -} - -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); - - if ((0 <= xy_iso.x) && (xy_iso.x < iso->world_width) && (0 <= xy_iso.y) && - (xy_iso.y < iso->world_height)) { - *xiso = (int)xy_iso.x; - *yiso = (int)xy_iso.y; - } else { - *xiso = -1; - *yiso = -1; - } -} -- cgit v1.2.3