From 21a0d0c1c424f7db90c3282aad4bf6ad4ef809b7 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 8 Jul 2023 14:37:29 -0700 Subject: Load tile maps and tile sets from files. --- gfx-iso/CMakeLists.txt | 11 +- gfx-iso/app/app.h | 11 + gfx-iso/app/checkerboard.c | 120 +++++++++ gfx-iso/app/checkerboard.h | 9 + gfx-iso/app/isogfx-demo.c | 67 +++++ gfx-iso/app/isogfx-demo.h | 9 + gfx-iso/app/main.c | 185 +++++++++++++ gfx-iso/asset/mkasset.py | 155 +++++++++++ gfx-iso/demo/isogfx-demo.c | 213 --------------- gfx-iso/include/isogfx/isogfx.h | 63 +++-- gfx-iso/src/isogfx.c | 561 ++++++++++++++++++++++++++++------------ 11 files changed, 1012 insertions(+), 392 deletions(-) create mode 100644 gfx-iso/app/app.h create mode 100644 gfx-iso/app/checkerboard.c create mode 100644 gfx-iso/app/checkerboard.h create mode 100644 gfx-iso/app/isogfx-demo.c create mode 100644 gfx-iso/app/isogfx-demo.h create mode 100644 gfx-iso/app/main.c create mode 100644 gfx-iso/asset/mkasset.py delete mode 100644 gfx-iso/demo/isogfx-demo.c diff --git a/gfx-iso/CMakeLists.txt b/gfx-iso/CMakeLists.txt index 8f95f7f..b57a83f 100644 --- a/gfx-iso/CMakeLists.txt +++ b/gfx-iso/CMakeLists.txt @@ -13,18 +13,21 @@ target_include_directories(isogfx PUBLIC include) target_link_libraries(isogfx PRIVATE + filesystem mempool) target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) # Demo -project(isogfx-demo) +project(isogfx-app) -add_executable(isogfx-demo - demo/isogfx-demo.c) +add_executable(isogfx-app + app/checkerboard.c + app/isogfx-demo.c + app/main.c) -target_link_libraries(isogfx-demo PRIVATE +target_link_libraries(isogfx-app PRIVATE gfx gfx-app isogfx) diff --git a/gfx-iso/app/app.h b/gfx-iso/app/app.h new file mode 100644 index 0000000..160da47 --- /dev/null +++ b/gfx-iso/app/app.h @@ -0,0 +1,11 @@ +#pragma once + +typedef struct IsoGfx IsoGfx; +typedef struct IsoGfxApp IsoGfxApp; + +typedef struct IsoGfxApp { + void* state; + void (*shutdown)(IsoGfx*, void* state); + void (*update)(IsoGfx*, void* state, double t, double dt); + void (*render)(IsoGfx*, void* state); +} IsoGfxApp; diff --git a/gfx-iso/app/checkerboard.c b/gfx-iso/app/checkerboard.c new file mode 100644 index 0000000..8b394c4 --- /dev/null +++ b/gfx-iso/app/checkerboard.c @@ -0,0 +1,120 @@ +#include "isogfx-demo.h" + +#include +#include + +#include +#include +#include +#include + +static const int TILE_WIDTH = 64; +static const int TILE_HEIGHT = TILE_WIDTH / 2; +static const int WORLD_WIDTH = 20; +static const int WORLD_HEIGHT = 20; + +static const TileDesc tile_set[] = { + {.type = TileFromColour, + .width = TILE_WIDTH, + .height = TILE_HEIGHT, + .colour = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46, .a = 0xff}}, + {.type = TileFromColour, + .width = TILE_WIDTH, + .height = TILE_HEIGHT, + .colour = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0, .a = 0xff}}, + {.type = TileFromColour, + .width = TILE_WIDTH, + .height = TILE_HEIGHT, + .colour = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84, .a = 0xff}}, +}; + +typedef enum Colour { + Black, + White, + Red, +} Colour; + +typedef struct State { + Tile red; + int xpick; + int ypick; +} State; + +static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) { + assert(iso); + for (int y = 0; y < isogfx_world_height(iso); ++y) { + for (int x = 0; x < isogfx_world_width(iso); ++x) { + const int odd_col = x & 1; + const int odd_row = y & 1; + const Tile value = (odd_row ^ odd_col) == 0 ? black : white; + isogfx_set_tile(iso, x, y, value); + } + } +} + +static void shutdown(IsoGfx* iso, void* app_state) { + assert(iso); + if (app_state) { + free(app_state); + } +} + +static void update(IsoGfx* iso, void* app_state, double t, double dt) { + assert(iso); + assert(app_state); + State* state = (State*)(app_state); + + double mouse_x, mouse_y; + gfx_app_get_mouse_position(&mouse_x, &mouse_y); + + isogfx_pick_tile(iso, mouse_x, mouse_y, &state->xpick, &state->ypick); + + printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); +} + +static void render(IsoGfx* iso, void* app_state) { + assert(iso); + assert(app_state); + State* state = (State*)(app_state); + + isogfx_render(iso); + if ((state->xpick != -1) && (state->ypick != -1)) { + isogfx_draw_tile(iso, state->xpick, state->ypick, state->red); + } +} + +bool make_checkerboard_app(IsoGfx* iso, IsoGfxApp* app) { + assert(iso); + assert(app); + + State* state = calloc(1, sizeof(State)); + if (!state) { + return false; + } + + if (!isogfx_make_world( + iso, &(WorldDesc){ + .tile_width = TILE_WIDTH, + .tile_height = TILE_HEIGHT, + .world_width = WORLD_WIDTH, + .world_height = WORLD_HEIGHT})) { + goto cleanup; + } + + const Tile black = isogfx_make_tile(iso, &tile_set[Black]); + const Tile white = isogfx_make_tile(iso, &tile_set[White]); + state->red = isogfx_make_tile(iso, &tile_set[Red]); + make_checkerboard(iso, black, white); + isogfx_render(iso); + + app->state = state; + app->shutdown = shutdown; + app->update = update; + app->render = render; + + return true; + +cleanup: + free(state); + return false; +} diff --git a/gfx-iso/app/checkerboard.h b/gfx-iso/app/checkerboard.h new file mode 100644 index 0000000..61725a5 --- /dev/null +++ b/gfx-iso/app/checkerboard.h @@ -0,0 +1,9 @@ +#pragma once + +#include "app.h" + +#include + +typedef struct IsoGfxApp IsoGfxApp; + +bool make_checkerboard_app(IsoGfx*, IsoGfxApp*); diff --git a/gfx-iso/app/isogfx-demo.c b/gfx-iso/app/isogfx-demo.c new file mode 100644 index 0000000..15ab6be --- /dev/null +++ b/gfx-iso/app/isogfx-demo.c @@ -0,0 +1,67 @@ +#include "isogfx-demo.h" + +#include +#include + +#include +#include +#include +#include + +typedef struct State { + int xpick; + int ypick; +} State; + +static void shutdown(IsoGfx* iso, void* app_state) { + assert(iso); + if (app_state) { + free(app_state); + } +} + +static void update(IsoGfx* iso, void* app_state, double t, double dt) { + assert(iso); + assert(app_state); + State* state = (State*)(app_state); + + double mouse_x, mouse_y; + gfx_app_get_mouse_position(&mouse_x, &mouse_y); + + isogfx_pick_tile(iso, mouse_x, mouse_y, &state->xpick, &state->ypick); + + // printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); +} + +static void render(IsoGfx* iso, void* app_state) { + assert(iso); + assert(app_state); + State* state = (State*)(app_state); + + isogfx_render(iso); +} + +bool make_demo_app(IsoGfx* iso, IsoGfxApp* app) { + assert(iso); + assert(app); + + State* state = calloc(1, sizeof(State)); + if (!state) { + return false; + } + + if (!isogfx_load_world(iso, "/home/jeanne/assets/tilemaps/demo1.tm")) { + goto cleanup; + } + + app->state = state; + app->shutdown = shutdown; + app->update = update; + app->render = render; + + return true; + +cleanup: + free(state); + return false; +} diff --git a/gfx-iso/app/isogfx-demo.h b/gfx-iso/app/isogfx-demo.h new file mode 100644 index 0000000..d099824 --- /dev/null +++ b/gfx-iso/app/isogfx-demo.h @@ -0,0 +1,9 @@ +#pragma once + +#include "app.h" + +#include + +typedef struct IsoGfxApp IsoGfxApp; + +bool make_demo_app(IsoGfx*, IsoGfxApp*); diff --git a/gfx-iso/app/main.c b/gfx-iso/app/main.c new file mode 100644 index 0000000..fa5a76b --- /dev/null +++ b/gfx-iso/app/main.c @@ -0,0 +1,185 @@ +#include "app.h" +#include "checkerboard.h" +#include "isogfx-demo.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static const int SCREEN_WIDTH = 1408; +static const int SCREEN_HEIGHT = 960; + +typedef struct State { + Gfx* gfx; + IsoGfx* iso; + IsoGfxApp app; + Texture* screen_texture; + Scene* scene; +} State; + +static bool init(const GfxAppDesc* desc, void** app_state) { + State* state = calloc(1, sizeof(State)); + if (!state) { + return false; + } + + if (!(state->iso = isogfx_new(&(IsoGfxDesc){ + .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) { + goto cleanup; + } + // if (!make_checkerboard_app(state->iso, &state->app)) { + // goto cleanup; + // } + if (!make_demo_app(state->iso, &state->app)) { + goto cleanup; + } + if (!(state->gfx = gfx_init())) { + goto cleanup; + } + RenderBackend* render_backend = gfx_get_render_backend(state->gfx); + + if (!(state->screen_texture = gfx_make_texture( + render_backend, &(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(render_backend); + if (!shader) { + goto cleanup; + } + + Geometry* geometry = gfx_make_quad_11(render_backend); + if (!geometry) { + goto cleanup; + } + + MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1}; + material_desc.uniforms[0] = (ShaderUniform){ + .type = UniformTexture, + .value.texture = state->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(); + if (!object) { + goto cleanup; + } + gfx_add_object_mesh(object, mesh); + + state->scene = gfx_make_scene(); + SceneNode* node = gfx_make_object_node(object); + SceneNode* root = gfx_get_scene_root(state->scene); + gfx_set_node_parent(node, root); + + *app_state = state; + return true; + +cleanup: + if (state->gfx) { + gfx_destroy(&state->gfx); + } + free(state); + return false; +} + +static void shutdown(void* app_state) { + assert(app_state); + State* state = (State*)(app_state); + + if (state->app.state) { + assert(state->iso); + (*state->app.shutdown)(state->iso, state->app.state); + } + isogfx_del(&state->iso); + gfx_destroy(&state->gfx); + free(app_state); +} + +static void update(void* app_state, double t, double dt) { + assert(app_state); + State* state = (State*)(app_state); + + assert(state->app.update); + (*state->app.update)(state->iso, state->app.state, t, dt); +} + +static void render(void* app_state) { + assert(app_state); + State* state = (State*)(app_state); + + assert(state->app.render); + (*state->app.render)(state->iso, state->app.state); + + const Pixel* screen = isogfx_get_screen_buffer(state->iso); + assert(screen); + gfx_update_texture( + state->screen_texture, &(TextureDataDesc){.pixels = screen}); + + RenderBackend* render_backend = gfx_get_render_backend(state->gfx); + Renderer* renderer = gfx_get_renderer(state->gfx); + + gfx_start_frame(render_backend); + gfx_render_scene( + renderer, &(RenderSceneParams){ + .mode = RenderDefault, .scene = state->scene, .camera = 0}); + gfx_end_frame(render_backend); +} + +static void resize(void* app_state, int width, int height) { + assert(app_state); + State* state = (State*)(app_state); + + RenderBackend* render_backend = gfx_get_render_backend(state->gfx); + gfx_set_viewport(render_backend, width, height); +} + +int main(int argc, const char** argv) { + const int initial_width = SCREEN_WIDTH; + const int initial_height = SCREEN_HEIGHT; + const int max_fps = 60; + + gfx_app_run( + &(GfxAppDesc){ + .argc = argc, + .argv = argv, + .width = initial_width, + .height = initial_height, + .max_fps = max_fps, + .update_delta_time = max_fps > 0 ? 1.0 / (double)max_fps : 0.0, + .title = "Isometric Renderer"}, + &(GfxAppCallbacks){ + .init = init, + .update = update, + .render = render, + .resize = resize, + .shutdown = shutdown}); + + return 0; +} diff --git a/gfx-iso/asset/mkasset.py b/gfx-iso/asset/mkasset.py new file mode 100644 index 0000000..15f7912 --- /dev/null +++ b/gfx-iso/asset/mkasset.py @@ -0,0 +1,155 @@ +# Converts tile sets and tile maps to binary formats (.TS, .TM) for the engine. +# +# Currently handles Tiled's .tsx and .tmx file formats. +# +# The output is a binary tile set file (.TS) or a binary tile map file (.TM). +import argparse +import ctypes +from PIL import Image +import sys +from xml.etree import ElementTree + +# Maximum length of path strings in .TS and .TM files. +MAX_PATH_LENGTH = 128 + + +def drop_extension(filepath): + return filepath[:filepath.rfind('.')] + + +def to_char_array(string, length): + """Convert a string to a fixed-length ASCII char array. + + The length of str must be at most length-1 so that the resulting string can + be null-terminated. + """ + assert (len(string) < length) + chars = string.encode("ascii") + nulls = ("\0" * (length - len(string))).encode("ascii") + return chars + nulls + + +def convert_tsx(input_filepath, output_filepath): + """Converts a Tiled .tsx tileset file to a .TS tile set file.""" + xml = ElementTree.parse(input_filepath) + root = xml.getroot() + + tile_count = int(root.attrib["tilecount"]) + max_tile_width = int(root.attrib["tilewidth"]) + max_tile_height = int(root.attrib["tileheight"]) + + print(f"Tile count: {tile_count}") + print(f"Max width: {max_tile_width}") + print(f"Max height: {max_tile_height}") + + with open(output_filepath, 'bw') as output: + output.write(ctypes.c_uint16(tile_count)) + output.write(ctypes.c_uint16(max_tile_width)) + output.write(ctypes.c_uint16(max_tile_height)) + + num_tile = 0 + for tile in root: + # Skip the "grid" and other non-tile elements. + if not tile.tag == "tile": + continue + + # Assuming tiles are numbered 0..N. + tile_id = int(tile.attrib["id"]) + assert (tile_id == num_tile) + num_tile += 1 + + image = tile[0] + tile_width = int(image.attrib["width"]) + tile_height = int(image.attrib["height"]) + tile_path = image.attrib["source"] + + output.write(ctypes.c_uint16(tile_width)) + output.write(ctypes.c_uint16(tile_height)) + + with Image.open(tile_path) as im: + bytes = im.convert('RGBA').tobytes() + output.write(bytes) + + +def convert_tmx(input_filepath, output_filepath): + """Converts a Tiled .tmx file to a .TM tile map file.""" + xml = ElementTree.parse(input_filepath) + root = xml.getroot() + + map_width = int(root.attrib["width"]) + map_height = int(root.attrib["height"]) + base_tile_width = int(root.attrib["tilewidth"]) + base_tile_height = int(root.attrib["tileheight"]) + num_layers = 1 + + print(f"Map width: {map_width}") + print(f"Map height: {map_height}") + print(f"Tile width: {base_tile_width}") + print(f"Tile height: {base_tile_height}") + + with open(output_filepath, 'bw') as output: + output.write(ctypes.c_uint16(map_width)) + output.write(ctypes.c_uint16(map_height)) + output.write(ctypes.c_uint16(base_tile_width)) + output.write(ctypes.c_uint16(base_tile_height)) + output.write(ctypes.c_uint16(num_layers)) + + tileset_path = None + + for child in root: + if child.tag == "tileset": + tileset = child + tileset_path = tileset.attrib["source"] + + print(f"Tile set: {tileset_path}") + + tileset_path = tileset_path.replace("tsx", "ts") + elif child.tag == "layer": + layer = child + layer_id = int(layer.attrib["id"]) + layer_width = int(layer.attrib["width"]) + layer_height = int(layer.attrib["height"]) + + print(f"Layer: {layer_id}") + print(f"Width: {layer_width}") + print(f"Height: {layer_height}") + + assert (tileset_path) + output.write(to_char_array(tileset_path, MAX_PATH_LENGTH)) + + # Assume the layer's dimensions matches the map's. + assert (layer_width == map_width) + assert (layer_height == map_height) + + data = layer[0] + # Handle other encodings later. + assert (data.attrib["encoding"] == "csv") + + csv = data.text.strip() + rows = csv.split('\n') + for row in rows: + tile_ids = [x.strip() for x in row.split(',') if x] + for tile_id in tile_ids: + output.write(ctypes.c_uint16(int(tile_id))) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("input", help="Input file (.tsx, .tmx)") + args = parser.parse_args() + + output_filepath_no_ext = drop_extension(args.input) + if ".tsx" in args.input: + output_filepath = output_filepath_no_ext + ".ts" + convert_tsx(args.input, output_filepath) + elif ".tmx" in args.input: + output_filepath = output_filepath_no_ext + ".tm" + convert_tmx(args.input, output_filepath) + else: + print(f"Unhandled file format: {args.input}") + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/gfx-iso/demo/isogfx-demo.c b/gfx-iso/demo/isogfx-demo.c deleted file mode 100644 index d6c1ab0..0000000 --- a/gfx-iso/demo/isogfx-demo.c +++ /dev/null @@ -1,213 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -static const int SCREEN_WIDTH = 1408; -static const int SCREEN_HEIGHT = 960; -static const int TILE_WIDTH = 64; -static const int TILE_HEIGHT = TILE_WIDTH / 2; -static const int WORLD_WIDTH = 20; -static const int WORLD_HEIGHT = 20; - -static const Pixel BLACK = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46}; -static const Pixel WHITE = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0}; -static const Pixel RED = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84}; - -typedef struct State { - Gfx* gfx; - IsoGfx* iso; - Tile red; - int xpick; - int ypick; - Texture* screen_texture; - Scene* scene; -} State; - -static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) { - assert(iso); - for (int y = 0; y < isogfx_world_height(iso); ++y) { - for (int x = 0; x < isogfx_world_width(iso); ++x) { - const int odd_col = x & 1; - const int odd_row = y & 1; - const Tile value = (odd_row ^ odd_col) == 0 ? black : white; - isogfx_set_tile(iso, x, y, value); - } - } -} - -static bool init(const GfxAppDesc* desc, void** app_state) { - State* state = calloc(1, sizeof(State)); - if (!state) { - return false; - } - - if (!(state->iso = isogfx_new(&(IsoGfxDesc){ - .screen_width = SCREEN_WIDTH, - .screen_height = SCREEN_HEIGHT, - .tile_width = TILE_WIDTH, - .tile_height = TILE_HEIGHT, - .world_width = WORLD_WIDTH, - .world_height = WORLD_HEIGHT}))) { - goto cleanup; - } - if (!(state->gfx = gfx_init())) { - goto cleanup; - } - RenderBackend* render_backend = gfx_get_render_backend(state->gfx); - - if (!(state->screen_texture = gfx_make_texture( - render_backend, &(TextureDesc){ - .width = SCREEN_WIDTH, - .height = SCREEN_HEIGHT, - .dimension = Texture2D, - .format = TextureRGB8, - .filtering = NearestFiltering, - .wrap = ClampToEdge, - .mipmaps = false}))) { - goto cleanup; - } - - ShaderProgram* shader = gfx_make_view_texture_shader(render_backend); - if (!shader) { - goto cleanup; - } - - Geometry* geometry = gfx_make_quad_11(render_backend); - if (!geometry) { - goto cleanup; - } - - MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1}; - material_desc.uniforms[0] = (ShaderUniform){ - .type = UniformTexture, - .value.texture = state->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(); - if (!object) { - goto cleanup; - } - gfx_add_object_mesh(object, mesh); - - state->scene = gfx_make_scene(); - SceneNode* node = gfx_make_object_node(object); - SceneNode* root = gfx_get_scene_root(state->scene); - gfx_set_node_parent(node, root); - - const Tile black = isogfx_make_tile( - state->iso, &(TileDesc){.type = TileFromColour, .colour = BLACK}); - const Tile white = isogfx_make_tile( - state->iso, &(TileDesc){.type = TileFromColour, .colour = WHITE}); - state->red = isogfx_make_tile( - state->iso, &(TileDesc){.type = TileFromColour, .colour = RED}); - make_checkerboard(state->iso, black, white); - isogfx_render(state->iso); - - *app_state = state; - return true; - -cleanup: - if (state->gfx) { - gfx_destroy(&state->gfx); - } - free(state); - return false; -} - -static void shutdown(void* app_state) { - assert(app_state); - State* state = (State*)(app_state); - isogfx_del(&state->iso); - gfx_destroy(&state->gfx); - free(app_state); -} - -static void update(void* app_state, double t, double dt) { - assert(app_state); - State* state = (State*)(app_state); - - double mouse_x, mouse_y; - gfx_app_get_mouse_position(&mouse_x, &mouse_y); - - isogfx_pick_tile(state->iso, mouse_x, mouse_y, &state->xpick, &state->ypick); - - printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); -} - -static void render(void* app_state) { - assert(app_state); - State* state = (State*)(app_state); - - isogfx_render(state->iso); - if ((state->xpick != -1) && (state->ypick != -1)) { - isogfx_draw_tile(state->iso, state->xpick, state->ypick, state->red); - } - - const Pixel* screen = isogfx_get_screen_buffer(state->iso); - assert(screen); - gfx_update_texture( - state->screen_texture, &(TextureDataDesc){.pixels = screen}); - - RenderBackend* render_backend = gfx_get_render_backend(state->gfx); - Renderer* renderer = gfx_get_renderer(state->gfx); - - gfx_start_frame(render_backend); - gfx_render_scene( - renderer, &(RenderSceneParams){ - .mode = RenderDefault, .scene = state->scene, .camera = 0}); - gfx_end_frame(render_backend); -} - -static void resize(void* app_state, int width, int height) { - assert(app_state); - State* state = (State*)(app_state); - - RenderBackend* render_backend = gfx_get_render_backend(state->gfx); - gfx_set_viewport(render_backend, width, height); -} - -int main(int argc, const char** argv) { - const int initial_width = SCREEN_WIDTH; - const int initial_height = SCREEN_HEIGHT; - const int max_fps = 60; - - gfx_app_run( - &(GfxAppDesc){ - .argc = argc, - .argv = argv, - .width = initial_width, - .height = initial_height, - .max_fps = max_fps, - .update_delta_time = max_fps > 0 ? 1.0 / (double)max_fps : 0.0, - .title = "Isometric Renderer"}, - &(GfxAppCallbacks){ - .init = init, - .update = update, - .render = render, - .resize = resize, - .shutdown = shutdown}); - - return 0; -} diff --git a/gfx-iso/include/isogfx/isogfx.h b/gfx-iso/include/isogfx/isogfx.h index a5f7770..22c8fd5 100644 --- a/gfx-iso/include/isogfx/isogfx.h +++ b/gfx-iso/include/isogfx/isogfx.h @@ -3,64 +3,97 @@ */ #pragma once +#include #include typedef struct IsoGfx IsoGfx; -typedef uint8_t Tile; +/// Tile handle. +typedef uint16_t Tile; + +/// Colour channel. typedef uint8_t Channel; typedef struct Pixel { - Channel r, g, b; + Channel r, g, b, a; } Pixel; typedef enum TileDescType { TileFromColour, TileFromFile, - TileFromMemory + TileFromMemory, } TileDescType; typedef struct TileDesc { TileDescType type; + int width; /// Tile width in pixels. + int height; /// Tile height in pixels. union { - Pixel colour; + Pixel colour; /// Constant colour tile. struct { const char* path; } file; struct { - const void* data; + const uint8_t* data; /// sizeof(Pixel) * width * height } mem; }; } TileDesc; +typedef struct WorldDesc { + int tile_width; /// Base tile width in pixels. + int tile_height; /// Base tile height in pixels. + int world_width; /// World width in tiles. + int world_height; /// World height in tiles. + int max_num_tiles; /// 0 for an implementation-defined default. +} WorldDesc; + typedef struct IsoGfxDesc { - int screen_width; - int screen_height; - int tile_width; - int tile_height; - int world_width; - int world_height; - int max_num_tiles; // 0 for an implementation-defined default. + int screen_width; /// Screen width in pixels. + int screen_height; /// Screen height in pixels. } IsoGfxDesc; +/// Create a new isometric graphics engine. IsoGfx* isogfx_new(const IsoGfxDesc*); +/// Destroy the isometric graphics engine. void isogfx_del(IsoGfx**); +/// Create an empty world. +bool isogfx_make_world(IsoGfx*, const WorldDesc*); + +/// Load a world from a tile map (.TM) file. +bool isogfx_load_world(IsoGfx*, const char* filepath); + +/// Return the world's width. +int isogfx_world_width(const IsoGfx*); + +/// Return the world's height. +int isogfx_world_height(const IsoGfx*); + +/// Create a new tile. Tile isogfx_make_tile(IsoGfx*, const TileDesc*); +/// Set the tile at position (x,y). void isogfx_set_tile(IsoGfx*, int x, int y, Tile); +/// Set the tiles in positions in the range (x0,y0) - (x1,y1). void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); +/// Translate Cartesian to isometric coordinates. void isogfx_pick_tile( const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso); +/// Render the world. void isogfx_render(IsoGfx*); +/// Draw/overlay a tile at position (x,y). +/// +/// This function just renders a tile at position (x,y) and should be called +/// after isogfx_render() to obtain the correct result. To set the tile at +/// position (x,y) instead, use isogfx_set_tile(). void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); +/// Return a pointer to the internal colour buffer. +/// +/// Call after each call to isogfx_render() to retrieve the render output. const Pixel* isogfx_get_screen_buffer(const IsoGfx*); - -int isogfx_world_width(const IsoGfx*); -int isogfx_world_height(const IsoGfx*); diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c index b38efe7..17b88b2 100644 --- a/gfx-iso/src/isogfx.c +++ b/gfx-iso/src/isogfx.c @@ -1,36 +1,106 @@ #include +#include #include +#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 +// ----------------------------------------------------------------------------- +// 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))); +} + +// ----------------------------------------------------------------------------- +// Renderer state. +// ----------------------------------------------------------------------------- + +// typedef Ts_Tile TileData; + typedef struct TileData { - Pixel pixels[1]; // Dynamically allocated. + uint16_t width; + uint16_t height; + uint16_t num_blocks; // Number of pixel blocks in the pixels mempool. + uint16_t pixels_index; // Offset into the pixels mempool. } TileData; DEF_MEMPOOL_DYN(TilePool, TileData) +DEF_MEMPOOL_DYN(PixelPool, Pixel) 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; + int screen_width; + int screen_height; + int tile_width; + int tile_height; + int world_width; + int world_height; + Tile* world; + Pixel* screen; + TilePool tiles; + PixelPool pixels; } IsoGfx; +// ----------------------------------------------------------------------------- +// Math and world / tile / screen access. +// ----------------------------------------------------------------------------- + typedef struct ivec2 { int x, y; } ivec2; @@ -70,38 +140,27 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { .y = (-one_over_s * x + one_over_t * cart.y)}; } -Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { +static const Pixel* tile_xy_const_ref( + 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]; + assert(x < tile->width); + assert(y < tile->height); + return &mempool_get_block( + &iso->pixels, tile->pixels_index)[y * 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 Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { + return *tile_xy_const_ref(iso, tile, x, y); } -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 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 Tile* world_xy_mut(IsoGfx* iso, int x, int y) { +static inline const Tile* world_xy_const_ref(const IsoGfx* iso, int x, int y) { assert(iso); assert(x >= 0); assert(y >= 0); @@ -110,16 +169,16 @@ static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { 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 Tile world_xy(const IsoGfx* iso, int x, int y) { + return *world_xy_const_ref(iso, x, y); } -static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int 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); @@ -128,169 +187,279 @@ static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { return &iso->screen[y * iso->screen_width + x]; } -static void draw_tile(IsoGfx* iso, ivec2 origin, 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 = origin.x + px; - const int sy = origin.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 inline Pixel screen_xy(IsoGfx* iso, int x, int y) { + return *screen_xy_const_ref(iso, x, y); } -static void draw(IsoGfx* iso) { - assert(iso); - - const int W = iso->screen_width; - const int H = iso->screen_height; +static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { + return (Pixel*)screen_xy_const_ref(iso, x, y); +} - memset(iso->screen, 0, W * H * sizeof(Pixel)); +// ----------------------------------------------------------------------------- +// Renderer, world and tile management. +// ----------------------------------------------------------------------------- - 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}; +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); - // 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, 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); - } + IsoGfx* iso = calloc(1, sizeof(IsoGfx)); + if (!iso) { + return 0; } -} - -/// 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; + iso->screen_width = desc->screen_width; + iso->screen_height = desc->screen_height; - // Top half. - iso->tile_mask[y * iso->tile_width + x] = val; + const int screen_size = desc->screen_width * desc->screen_height; - // 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; - } + if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { + goto cleanup; } + + return iso; + +cleanup: + isogfx_del(&iso); + return 0; } -/// Creates a tile with a constant colour. -static void make_tile_from_colour( - const IsoGfx* iso, Pixel colour, TileData* tile) { +/// Destroy the world and its tile set. +static void destroy_world(IsoGfx* iso) { assert(iso); - assert(tile); + if (iso->world) { + free(iso->world); + iso->world = 0; + } + mempool_del(&iso->tiles); + mempool_del(&iso->pixels); +} - 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; +void isogfx_del(IsoGfx** pIso) { + assert(pIso); + IsoGfx* iso = *pIso; + if (iso) { + destroy_world(iso); + if (iso->screen) { + free(iso->screen); + iso->screen = 0; } + free(iso); + *pIso = 0; } } -IsoGfx* isogfx_new(const IsoGfxDesc* desc) { - assert(desc->screen_width > 0); - assert(desc->screen_height > 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->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; + // Handle recreation by destroying the previous world. + destroy_world(iso); - 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; + 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 (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { + if (!mempool_make_dyn(&iso->tiles, tile_pool_size, tile_size_bytes)) { + goto cleanup; + } + + return true; + +cleanup: + destroy_world(iso); + mempool_del(&iso->tiles); + 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; } - if (!(iso->tile_mask = calloc(tile_size, sizeof(uint8_t)))) { + + // 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, iso->max_num_tiles, tile_size_bytes)) { + if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) { + goto cleanup; + } + if (!mempool_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) { goto cleanup; } - make_tile_mask(iso); + // 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 (!make_relative_path(MAX_PATH_LENGTH, filepath, ts_path, ts_path_cwd)) { + goto cleanup; + } - return iso; + 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); + + const uint16_t tile_size = tile->width * tile->height; + + // TODO: Add function in mempool to alloc N consecutive blocks. + const int num_blocks = tile_size / base_tile_size; + Pixel* pixels = mempool_alloc(&iso->pixels); + assert(pixels); + // This is ugly and assumes that blocks are allocated consecutively. + for (int b = 1; b < num_blocks; ++b) { + Pixel* block = mempool_alloc(&iso->pixels); + assert(block); + } + memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel)); -cleanup: - isogfx_del(&iso); - return 0; -} + TileData* tile_data = mempool_alloc(&iso->tiles); + assert(tile_data); + tile_data->width = tile->width; + tile_data->height = tile->height; + tile_data->num_blocks = (uint16_t)num_blocks; + tile_data->pixels_index = + (uint16_t)mempool_get_block_index(&iso->pixels, pixels); -void isogfx_del(IsoGfx** pIso) { - assert(pIso); - IsoGfx* iso = *pIso; - if (iso) { - if (iso->world) { - free(iso->world); + tile = ts_tileset_get_next_tile(tileset, tile); } - if (iso->screen) { - free(iso->screen); + + 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; } - if (iso->tile_mask) { - free(iso->tile_mask); + + 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; } - mempool_del(&iso->tiles); - free(iso); } } 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. + tile->width = desc->width; + tile->height = desc->height; + switch (desc->type) { case TileFromColour: make_tile_from_colour(iso, desc->colour, tile); @@ -311,6 +480,88 @@ void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { *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); + } + } +} + +// ----------------------------------------------------------------------------- +// Rendering and picking. +// ----------------------------------------------------------------------------- + +static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { + assert(iso); + + const TileData* tile_data = mempool_get_block(&iso->tiles, tile); + assert(tile_data); + + // Tile can exceed screen bounds, so we must clip it. +#define max(a, b) (a > b ? a : b) + const int py_offset = max(0, (int)tile_data->height - origin.y); + origin.y = max(0, origin.y - (int)tile_data->height); + + // Clip along Y and X as we draw. + for (int py = py_offset; + (py < tile_data->height) && (origin.y + py < iso->screen_height); ++py) { + const int sy = origin.y + py - py_offset; + for (int px = 0; + (px < tile_data->width) && (origin.x + px < iso->screen_width); ++px) { + const Pixel colour = tile_xy(iso, tile_data, px, py); + if (colour.a > 0) { + const int sx = origin.x + px; + *screen_xy_mut(iso, sx, sy) = colour; + } + } + } + + // for (int py = 0; py < tile_data->height; ++py) { + // for (int px = 0; px < tile_data->width; ++px) { + // const Pixel colour = tile_xy(iso, tile_data, px, py); + // if (colour.a > 0) { + // const int sx = origin.x + px; + // const int sy = origin.y + py; + // if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) && + // (sy < iso->screen_height)) { + // *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 o = { + (iso->screen_width / 2) - (iso->tile_width / 2), iso->tile_height}; + 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: 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 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); + } + } +} + void isogfx_pick_tile( const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { assert(iso); @@ -356,13 +607,3 @@ 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