diff options
| -rw-r--r-- | gfx-iso/CMakeLists.txt | 11 | ||||
| -rw-r--r-- | gfx-iso/app/app.h | 11 | ||||
| -rw-r--r-- | gfx-iso/app/checkerboard.c | 120 | ||||
| -rw-r--r-- | gfx-iso/app/checkerboard.h | 9 | ||||
| -rw-r--r-- | gfx-iso/app/isogfx-demo.c | 67 | ||||
| -rw-r--r-- | gfx-iso/app/isogfx-demo.h | 9 | ||||
| -rw-r--r-- | gfx-iso/app/main.c (renamed from gfx-iso/demo/isogfx-demo.c) | 80 | ||||
| -rw-r--r-- | gfx-iso/asset/mkasset.py | 155 | ||||
| -rw-r--r-- | gfx-iso/include/isogfx/isogfx.h | 63 | ||||
| -rw-r--r-- | gfx-iso/src/isogfx.c | 561 |
10 files changed, 853 insertions, 233 deletions
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 | |||
| 13 | include) | 13 | include) |
| 14 | 14 | ||
| 15 | target_link_libraries(isogfx PRIVATE | 15 | target_link_libraries(isogfx PRIVATE |
| 16 | filesystem | ||
| 16 | mempool) | 17 | mempool) |
| 17 | 18 | ||
| 18 | target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) | 19 | target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) |
| 19 | 20 | ||
| 20 | # Demo | 21 | # Demo |
| 21 | 22 | ||
| 22 | project(isogfx-demo) | 23 | project(isogfx-app) |
| 23 | 24 | ||
| 24 | add_executable(isogfx-demo | 25 | add_executable(isogfx-app |
| 25 | demo/isogfx-demo.c) | 26 | app/checkerboard.c |
| 27 | app/isogfx-demo.c | ||
| 28 | app/main.c) | ||
| 26 | 29 | ||
| 27 | target_link_libraries(isogfx-demo PRIVATE | 30 | target_link_libraries(isogfx-app PRIVATE |
| 28 | gfx | 31 | gfx |
| 29 | gfx-app | 32 | gfx-app |
| 30 | isogfx) | 33 | 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 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | typedef struct IsoGfx IsoGfx; | ||
| 4 | typedef struct IsoGfxApp IsoGfxApp; | ||
| 5 | |||
| 6 | typedef struct IsoGfxApp { | ||
| 7 | void* state; | ||
| 8 | void (*shutdown)(IsoGfx*, void* state); | ||
| 9 | void (*update)(IsoGfx*, void* state, double t, double dt); | ||
| 10 | void (*render)(IsoGfx*, void* state); | ||
| 11 | } 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 @@ | |||
| 1 | #include "isogfx-demo.h" | ||
| 2 | |||
| 3 | #include <gfx/gfx_app.h> | ||
| 4 | #include <isogfx/isogfx.h> | ||
| 5 | |||
| 6 | #include <assert.h> | ||
| 7 | #include <stdbool.h> | ||
| 8 | #include <stdio.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | |||
| 11 | static const int TILE_WIDTH = 64; | ||
| 12 | static const int TILE_HEIGHT = TILE_WIDTH / 2; | ||
| 13 | static const int WORLD_WIDTH = 20; | ||
| 14 | static const int WORLD_HEIGHT = 20; | ||
| 15 | |||
| 16 | static const TileDesc tile_set[] = { | ||
| 17 | {.type = TileFromColour, | ||
| 18 | .width = TILE_WIDTH, | ||
| 19 | .height = TILE_HEIGHT, | ||
| 20 | .colour = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46, .a = 0xff}}, | ||
| 21 | {.type = TileFromColour, | ||
| 22 | .width = TILE_WIDTH, | ||
| 23 | .height = TILE_HEIGHT, | ||
| 24 | .colour = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0, .a = 0xff}}, | ||
| 25 | {.type = TileFromColour, | ||
| 26 | .width = TILE_WIDTH, | ||
| 27 | .height = TILE_HEIGHT, | ||
| 28 | .colour = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84, .a = 0xff}}, | ||
| 29 | }; | ||
| 30 | |||
| 31 | typedef enum Colour { | ||
| 32 | Black, | ||
| 33 | White, | ||
| 34 | Red, | ||
| 35 | } Colour; | ||
| 36 | |||
| 37 | typedef struct State { | ||
| 38 | Tile red; | ||
| 39 | int xpick; | ||
| 40 | int ypick; | ||
| 41 | } State; | ||
| 42 | |||
| 43 | static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) { | ||
| 44 | assert(iso); | ||
| 45 | for (int y = 0; y < isogfx_world_height(iso); ++y) { | ||
| 46 | for (int x = 0; x < isogfx_world_width(iso); ++x) { | ||
| 47 | const int odd_col = x & 1; | ||
| 48 | const int odd_row = y & 1; | ||
| 49 | const Tile value = (odd_row ^ odd_col) == 0 ? black : white; | ||
| 50 | isogfx_set_tile(iso, x, y, value); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | static void shutdown(IsoGfx* iso, void* app_state) { | ||
| 56 | assert(iso); | ||
| 57 | if (app_state) { | ||
| 58 | free(app_state); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | static void update(IsoGfx* iso, void* app_state, double t, double dt) { | ||
| 63 | assert(iso); | ||
| 64 | assert(app_state); | ||
| 65 | State* state = (State*)(app_state); | ||
| 66 | |||
| 67 | double mouse_x, mouse_y; | ||
| 68 | gfx_app_get_mouse_position(&mouse_x, &mouse_y); | ||
| 69 | |||
| 70 | isogfx_pick_tile(iso, mouse_x, mouse_y, &state->xpick, &state->ypick); | ||
| 71 | |||
| 72 | printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); | ||
| 73 | } | ||
| 74 | |||
| 75 | static void render(IsoGfx* iso, void* app_state) { | ||
| 76 | assert(iso); | ||
| 77 | assert(app_state); | ||
| 78 | State* state = (State*)(app_state); | ||
| 79 | |||
| 80 | isogfx_render(iso); | ||
| 81 | if ((state->xpick != -1) && (state->ypick != -1)) { | ||
| 82 | isogfx_draw_tile(iso, state->xpick, state->ypick, state->red); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | bool make_checkerboard_app(IsoGfx* iso, IsoGfxApp* app) { | ||
| 87 | assert(iso); | ||
| 88 | assert(app); | ||
| 89 | |||
| 90 | State* state = calloc(1, sizeof(State)); | ||
| 91 | if (!state) { | ||
| 92 | return false; | ||
| 93 | } | ||
| 94 | |||
| 95 | if (!isogfx_make_world( | ||
| 96 | iso, &(WorldDesc){ | ||
| 97 | .tile_width = TILE_WIDTH, | ||
| 98 | .tile_height = TILE_HEIGHT, | ||
| 99 | .world_width = WORLD_WIDTH, | ||
| 100 | .world_height = WORLD_HEIGHT})) { | ||
| 101 | goto cleanup; | ||
| 102 | } | ||
| 103 | |||
| 104 | const Tile black = isogfx_make_tile(iso, &tile_set[Black]); | ||
| 105 | const Tile white = isogfx_make_tile(iso, &tile_set[White]); | ||
| 106 | state->red = isogfx_make_tile(iso, &tile_set[Red]); | ||
| 107 | make_checkerboard(iso, black, white); | ||
| 108 | isogfx_render(iso); | ||
| 109 | |||
| 110 | app->state = state; | ||
| 111 | app->shutdown = shutdown; | ||
| 112 | app->update = update; | ||
| 113 | app->render = render; | ||
| 114 | |||
| 115 | return true; | ||
| 116 | |||
| 117 | cleanup: | ||
| 118 | free(state); | ||
| 119 | return false; | ||
| 120 | } | ||
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 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "app.h" | ||
| 4 | |||
| 5 | #include <stdbool.h> | ||
| 6 | |||
| 7 | typedef struct IsoGfxApp IsoGfxApp; | ||
| 8 | |||
| 9 | 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 @@ | |||
| 1 | #include "isogfx-demo.h" | ||
| 2 | |||
| 3 | #include <gfx/gfx_app.h> | ||
| 4 | #include <isogfx/isogfx.h> | ||
| 5 | |||
| 6 | #include <assert.h> | ||
| 7 | #include <stdbool.h> | ||
| 8 | #include <stdio.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | |||
| 11 | typedef struct State { | ||
| 12 | int xpick; | ||
| 13 | int ypick; | ||
| 14 | } State; | ||
| 15 | |||
| 16 | static void shutdown(IsoGfx* iso, void* app_state) { | ||
| 17 | assert(iso); | ||
| 18 | if (app_state) { | ||
| 19 | free(app_state); | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | static void update(IsoGfx* iso, void* app_state, double t, double dt) { | ||
| 24 | assert(iso); | ||
| 25 | assert(app_state); | ||
| 26 | State* state = (State*)(app_state); | ||
| 27 | |||
| 28 | double mouse_x, mouse_y; | ||
| 29 | gfx_app_get_mouse_position(&mouse_x, &mouse_y); | ||
| 30 | |||
| 31 | isogfx_pick_tile(iso, mouse_x, mouse_y, &state->xpick, &state->ypick); | ||
| 32 | |||
| 33 | // printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); | ||
| 34 | } | ||
| 35 | |||
| 36 | static void render(IsoGfx* iso, void* app_state) { | ||
| 37 | assert(iso); | ||
| 38 | assert(app_state); | ||
| 39 | State* state = (State*)(app_state); | ||
| 40 | |||
| 41 | isogfx_render(iso); | ||
| 42 | } | ||
| 43 | |||
| 44 | bool make_demo_app(IsoGfx* iso, IsoGfxApp* app) { | ||
| 45 | assert(iso); | ||
| 46 | assert(app); | ||
| 47 | |||
| 48 | State* state = calloc(1, sizeof(State)); | ||
| 49 | if (!state) { | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | |||
| 53 | if (!isogfx_load_world(iso, "/home/jeanne/assets/tilemaps/demo1.tm")) { | ||
| 54 | goto cleanup; | ||
| 55 | } | ||
| 56 | |||
| 57 | app->state = state; | ||
| 58 | app->shutdown = shutdown; | ||
| 59 | app->update = update; | ||
| 60 | app->render = render; | ||
| 61 | |||
| 62 | return true; | ||
| 63 | |||
| 64 | cleanup: | ||
| 65 | free(state); | ||
| 66 | return false; | ||
| 67 | } | ||
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 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "app.h" | ||
| 4 | |||
| 5 | #include <stdbool.h> | ||
| 6 | |||
| 7 | typedef struct IsoGfxApp IsoGfxApp; | ||
| 8 | |||
| 9 | bool make_demo_app(IsoGfx*, IsoGfxApp*); | ||
diff --git a/gfx-iso/demo/isogfx-demo.c b/gfx-iso/app/main.c index d6c1ab0..fa5a76b 100644 --- a/gfx-iso/demo/isogfx-demo.c +++ b/gfx-iso/app/main.c | |||
| @@ -1,3 +1,7 @@ | |||
| 1 | #include "app.h" | ||
| 2 | #include "checkerboard.h" | ||
| 3 | #include "isogfx-demo.h" | ||
| 4 | |||
| 1 | #include <isogfx/isogfx.h> | 5 | #include <isogfx/isogfx.h> |
| 2 | 6 | ||
| 3 | #include <gfx/gfx.h> | 7 | #include <gfx/gfx.h> |
| @@ -10,42 +14,19 @@ | |||
| 10 | 14 | ||
| 11 | #include <assert.h> | 15 | #include <assert.h> |
| 12 | #include <stdbool.h> | 16 | #include <stdbool.h> |
| 13 | #include <stdio.h> | ||
| 14 | #include <stdlib.h> | 17 | #include <stdlib.h> |
| 15 | 18 | ||
| 16 | static const int SCREEN_WIDTH = 1408; | 19 | static const int SCREEN_WIDTH = 1408; |
| 17 | static const int SCREEN_HEIGHT = 960; | 20 | static const int SCREEN_HEIGHT = 960; |
| 18 | static const int TILE_WIDTH = 64; | ||
| 19 | static const int TILE_HEIGHT = TILE_WIDTH / 2; | ||
| 20 | static const int WORLD_WIDTH = 20; | ||
| 21 | static const int WORLD_HEIGHT = 20; | ||
| 22 | |||
| 23 | static const Pixel BLACK = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46}; | ||
| 24 | static const Pixel WHITE = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0}; | ||
| 25 | static const Pixel RED = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84}; | ||
| 26 | 21 | ||
| 27 | typedef struct State { | 22 | typedef struct State { |
| 28 | Gfx* gfx; | 23 | Gfx* gfx; |
| 29 | IsoGfx* iso; | 24 | IsoGfx* iso; |
| 30 | Tile red; | 25 | IsoGfxApp app; |
| 31 | int xpick; | 26 | Texture* screen_texture; |
| 32 | int ypick; | 27 | Scene* scene; |
| 33 | Texture* screen_texture; | ||
| 34 | Scene* scene; | ||
| 35 | } State; | 28 | } State; |
| 36 | 29 | ||
| 37 | static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) { | ||
| 38 | assert(iso); | ||
| 39 | for (int y = 0; y < isogfx_world_height(iso); ++y) { | ||
| 40 | for (int x = 0; x < isogfx_world_width(iso); ++x) { | ||
| 41 | const int odd_col = x & 1; | ||
| 42 | const int odd_row = y & 1; | ||
| 43 | const Tile value = (odd_row ^ odd_col) == 0 ? black : white; | ||
| 44 | isogfx_set_tile(iso, x, y, value); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | static bool init(const GfxAppDesc* desc, void** app_state) { | 30 | static bool init(const GfxAppDesc* desc, void** app_state) { |
| 50 | State* state = calloc(1, sizeof(State)); | 31 | State* state = calloc(1, sizeof(State)); |
| 51 | if (!state) { | 32 | if (!state) { |
| @@ -53,12 +34,13 @@ static bool init(const GfxAppDesc* desc, void** app_state) { | |||
| 53 | } | 34 | } |
| 54 | 35 | ||
| 55 | if (!(state->iso = isogfx_new(&(IsoGfxDesc){ | 36 | if (!(state->iso = isogfx_new(&(IsoGfxDesc){ |
| 56 | .screen_width = SCREEN_WIDTH, | 37 | .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) { |
| 57 | .screen_height = SCREEN_HEIGHT, | 38 | goto cleanup; |
| 58 | .tile_width = TILE_WIDTH, | 39 | } |
| 59 | .tile_height = TILE_HEIGHT, | 40 | // if (!make_checkerboard_app(state->iso, &state->app)) { |
| 60 | .world_width = WORLD_WIDTH, | 41 | // goto cleanup; |
| 61 | .world_height = WORLD_HEIGHT}))) { | 42 | // } |
| 43 | if (!make_demo_app(state->iso, &state->app)) { | ||
| 62 | goto cleanup; | 44 | goto cleanup; |
| 63 | } | 45 | } |
| 64 | if (!(state->gfx = gfx_init())) { | 46 | if (!(state->gfx = gfx_init())) { |
| @@ -71,7 +53,7 @@ static bool init(const GfxAppDesc* desc, void** app_state) { | |||
| 71 | .width = SCREEN_WIDTH, | 53 | .width = SCREEN_WIDTH, |
| 72 | .height = SCREEN_HEIGHT, | 54 | .height = SCREEN_HEIGHT, |
| 73 | .dimension = Texture2D, | 55 | .dimension = Texture2D, |
| 74 | .format = TextureRGB8, | 56 | .format = TextureSRGBA8, |
| 75 | .filtering = NearestFiltering, | 57 | .filtering = NearestFiltering, |
| 76 | .wrap = ClampToEdge, | 58 | .wrap = ClampToEdge, |
| 77 | .mipmaps = false}))) { | 59 | .mipmaps = false}))) { |
| @@ -116,15 +98,6 @@ static bool init(const GfxAppDesc* desc, void** app_state) { | |||
| 116 | SceneNode* root = gfx_get_scene_root(state->scene); | 98 | SceneNode* root = gfx_get_scene_root(state->scene); |
| 117 | gfx_set_node_parent(node, root); | 99 | gfx_set_node_parent(node, root); |
| 118 | 100 | ||
| 119 | const Tile black = isogfx_make_tile( | ||
| 120 | state->iso, &(TileDesc){.type = TileFromColour, .colour = BLACK}); | ||
| 121 | const Tile white = isogfx_make_tile( | ||
| 122 | state->iso, &(TileDesc){.type = TileFromColour, .colour = WHITE}); | ||
| 123 | state->red = isogfx_make_tile( | ||
| 124 | state->iso, &(TileDesc){.type = TileFromColour, .colour = RED}); | ||
| 125 | make_checkerboard(state->iso, black, white); | ||
| 126 | isogfx_render(state->iso); | ||
| 127 | |||
| 128 | *app_state = state; | 101 | *app_state = state; |
| 129 | return true; | 102 | return true; |
| 130 | 103 | ||
| @@ -139,6 +112,11 @@ cleanup: | |||
| 139 | static void shutdown(void* app_state) { | 112 | static void shutdown(void* app_state) { |
| 140 | assert(app_state); | 113 | assert(app_state); |
| 141 | State* state = (State*)(app_state); | 114 | State* state = (State*)(app_state); |
| 115 | |||
| 116 | if (state->app.state) { | ||
| 117 | assert(state->iso); | ||
| 118 | (*state->app.shutdown)(state->iso, state->app.state); | ||
| 119 | } | ||
| 142 | isogfx_del(&state->iso); | 120 | isogfx_del(&state->iso); |
| 143 | gfx_destroy(&state->gfx); | 121 | gfx_destroy(&state->gfx); |
| 144 | free(app_state); | 122 | free(app_state); |
| @@ -148,22 +126,16 @@ static void update(void* app_state, double t, double dt) { | |||
| 148 | assert(app_state); | 126 | assert(app_state); |
| 149 | State* state = (State*)(app_state); | 127 | State* state = (State*)(app_state); |
| 150 | 128 | ||
| 151 | double mouse_x, mouse_y; | 129 | assert(state->app.update); |
| 152 | gfx_app_get_mouse_position(&mouse_x, &mouse_y); | 130 | (*state->app.update)(state->iso, state->app.state, t, dt); |
| 153 | |||
| 154 | isogfx_pick_tile(state->iso, mouse_x, mouse_y, &state->xpick, &state->ypick); | ||
| 155 | |||
| 156 | printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); | ||
| 157 | } | 131 | } |
| 158 | 132 | ||
| 159 | static void render(void* app_state) { | 133 | static void render(void* app_state) { |
| 160 | assert(app_state); | 134 | assert(app_state); |
| 161 | State* state = (State*)(app_state); | 135 | State* state = (State*)(app_state); |
| 162 | 136 | ||
| 163 | isogfx_render(state->iso); | 137 | assert(state->app.render); |
| 164 | if ((state->xpick != -1) && (state->ypick != -1)) { | 138 | (*state->app.render)(state->iso, state->app.state); |
| 165 | isogfx_draw_tile(state->iso, state->xpick, state->ypick, state->red); | ||
| 166 | } | ||
| 167 | 139 | ||
| 168 | const Pixel* screen = isogfx_get_screen_buffer(state->iso); | 140 | const Pixel* screen = isogfx_get_screen_buffer(state->iso); |
| 169 | assert(screen); | 141 | assert(screen); |
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 @@ | |||
| 1 | # Converts tile sets and tile maps to binary formats (.TS, .TM) for the engine. | ||
| 2 | # | ||
| 3 | # Currently handles Tiled's .tsx and .tmx file formats. | ||
| 4 | # | ||
| 5 | # The output is a binary tile set file (.TS) or a binary tile map file (.TM). | ||
| 6 | import argparse | ||
| 7 | import ctypes | ||
| 8 | from PIL import Image | ||
| 9 | import sys | ||
| 10 | from xml.etree import ElementTree | ||
| 11 | |||
| 12 | # Maximum length of path strings in .TS and .TM files. | ||
| 13 | MAX_PATH_LENGTH = 128 | ||
| 14 | |||
| 15 | |||
| 16 | def drop_extension(filepath): | ||
| 17 | return filepath[:filepath.rfind('.')] | ||
| 18 | |||
| 19 | |||
| 20 | def to_char_array(string, length): | ||
| 21 | """Convert a string to a fixed-length ASCII char array. | ||
| 22 | |||
| 23 | The length of str must be at most length-1 so that the resulting string can | ||
| 24 | be null-terminated. | ||
| 25 | """ | ||
| 26 | assert (len(string) < length) | ||
| 27 | chars = string.encode("ascii") | ||
| 28 | nulls = ("\0" * (length - len(string))).encode("ascii") | ||
| 29 | return chars + nulls | ||
| 30 | |||
| 31 | |||
| 32 | def convert_tsx(input_filepath, output_filepath): | ||
| 33 | """Converts a Tiled .tsx tileset file to a .TS tile set file.""" | ||
| 34 | xml = ElementTree.parse(input_filepath) | ||
| 35 | root = xml.getroot() | ||
| 36 | |||
| 37 | tile_count = int(root.attrib["tilecount"]) | ||
| 38 | max_tile_width = int(root.attrib["tilewidth"]) | ||
| 39 | max_tile_height = int(root.attrib["tileheight"]) | ||
| 40 | |||
| 41 | print(f"Tile count: {tile_count}") | ||
| 42 | print(f"Max width: {max_tile_width}") | ||
| 43 | print(f"Max height: {max_tile_height}") | ||
| 44 | |||
| 45 | with open(output_filepath, 'bw') as output: | ||
| 46 | output.write(ctypes.c_uint16(tile_count)) | ||
| 47 | output.write(ctypes.c_uint16(max_tile_width)) | ||
| 48 | output.write(ctypes.c_uint16(max_tile_height)) | ||
| 49 | |||
| 50 | num_tile = 0 | ||
| 51 | for tile in root: | ||
| 52 | # Skip the "grid" and other non-tile elements. | ||
| 53 | if not tile.tag == "tile": | ||
| 54 | continue | ||
| 55 | |||
| 56 | # Assuming tiles are numbered 0..N. | ||
| 57 | tile_id = int(tile.attrib["id"]) | ||
| 58 | assert (tile_id == num_tile) | ||
| 59 | num_tile += 1 | ||
| 60 | |||
| 61 | image = tile[0] | ||
| 62 | tile_width = int(image.attrib["width"]) | ||
| 63 | tile_height = int(image.attrib["height"]) | ||
| 64 | tile_path = image.attrib["source"] | ||
| 65 | |||
| 66 | output.write(ctypes.c_uint16(tile_width)) | ||
| 67 | output.write(ctypes.c_uint16(tile_height)) | ||
| 68 | |||
| 69 | with Image.open(tile_path) as im: | ||
| 70 | bytes = im.convert('RGBA').tobytes() | ||
| 71 | output.write(bytes) | ||
| 72 | |||
| 73 | |||
| 74 | def convert_tmx(input_filepath, output_filepath): | ||
| 75 | """Converts a Tiled .tmx file to a .TM tile map file.""" | ||
| 76 | xml = ElementTree.parse(input_filepath) | ||
| 77 | root = xml.getroot() | ||
| 78 | |||
| 79 | map_width = int(root.attrib["width"]) | ||
| 80 | map_height = int(root.attrib["height"]) | ||
| 81 | base_tile_width = int(root.attrib["tilewidth"]) | ||
| 82 | base_tile_height = int(root.attrib["tileheight"]) | ||
| 83 | num_layers = 1 | ||
| 84 | |||
| 85 | print(f"Map width: {map_width}") | ||
| 86 | print(f"Map height: {map_height}") | ||
| 87 | print(f"Tile width: {base_tile_width}") | ||
| 88 | print(f"Tile height: {base_tile_height}") | ||
| 89 | |||
| 90 | with open(output_filepath, 'bw') as output: | ||
| 91 | output.write(ctypes.c_uint16(map_width)) | ||
| 92 | output.write(ctypes.c_uint16(map_height)) | ||
| 93 | output.write(ctypes.c_uint16(base_tile_width)) | ||
| 94 | output.write(ctypes.c_uint16(base_tile_height)) | ||
| 95 | output.write(ctypes.c_uint16(num_layers)) | ||
| 96 | |||
| 97 | tileset_path = None | ||
| 98 | |||
| 99 | for child in root: | ||
| 100 | if child.tag == "tileset": | ||
| 101 | tileset = child | ||
| 102 | tileset_path = tileset.attrib["source"] | ||
| 103 | |||
| 104 | print(f"Tile set: {tileset_path}") | ||
| 105 | |||
| 106 | tileset_path = tileset_path.replace("tsx", "ts") | ||
| 107 | elif child.tag == "layer": | ||
| 108 | layer = child | ||
| 109 | layer_id = int(layer.attrib["id"]) | ||
| 110 | layer_width = int(layer.attrib["width"]) | ||
| 111 | layer_height = int(layer.attrib["height"]) | ||
| 112 | |||
| 113 | print(f"Layer: {layer_id}") | ||
| 114 | print(f"Width: {layer_width}") | ||
| 115 | print(f"Height: {layer_height}") | ||
| 116 | |||
| 117 | assert (tileset_path) | ||
| 118 | output.write(to_char_array(tileset_path, MAX_PATH_LENGTH)) | ||
| 119 | |||
| 120 | # Assume the layer's dimensions matches the map's. | ||
| 121 | assert (layer_width == map_width) | ||
| 122 | assert (layer_height == map_height) | ||
| 123 | |||
| 124 | data = layer[0] | ||
| 125 | # Handle other encodings later. | ||
| 126 | assert (data.attrib["encoding"] == "csv") | ||
| 127 | |||
| 128 | csv = data.text.strip() | ||
| 129 | rows = csv.split('\n') | ||
| 130 | for row in rows: | ||
| 131 | tile_ids = [x.strip() for x in row.split(',') if x] | ||
| 132 | for tile_id in tile_ids: | ||
| 133 | output.write(ctypes.c_uint16(int(tile_id))) | ||
| 134 | |||
| 135 | |||
| 136 | def main(): | ||
| 137 | parser = argparse.ArgumentParser() | ||
| 138 | parser.add_argument("input", help="Input file (.tsx, .tmx)") | ||
| 139 | args = parser.parse_args() | ||
| 140 | |||
| 141 | output_filepath_no_ext = drop_extension(args.input) | ||
| 142 | if ".tsx" in args.input: | ||
| 143 | output_filepath = output_filepath_no_ext + ".ts" | ||
| 144 | convert_tsx(args.input, output_filepath) | ||
| 145 | elif ".tmx" in args.input: | ||
| 146 | output_filepath = output_filepath_no_ext + ".tm" | ||
| 147 | convert_tmx(args.input, output_filepath) | ||
| 148 | else: | ||
| 149 | print(f"Unhandled file format: {args.input}") | ||
| 150 | |||
| 151 | return 0 | ||
| 152 | |||
| 153 | |||
| 154 | if __name__ == '__main__': | ||
| 155 | sys.exit(main()) | ||
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 @@ | |||
| 3 | */ | 3 | */ |
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <stdbool.h> | ||
| 6 | #include <stdint.h> | 7 | #include <stdint.h> |
| 7 | 8 | ||
| 8 | typedef struct IsoGfx IsoGfx; | 9 | typedef struct IsoGfx IsoGfx; |
| 9 | 10 | ||
| 10 | typedef uint8_t Tile; | 11 | /// Tile handle. |
| 12 | typedef uint16_t Tile; | ||
| 13 | |||
| 14 | /// Colour channel. | ||
| 11 | typedef uint8_t Channel; | 15 | typedef uint8_t Channel; |
| 12 | 16 | ||
| 13 | typedef struct Pixel { | 17 | typedef struct Pixel { |
| 14 | Channel r, g, b; | 18 | Channel r, g, b, a; |
| 15 | } Pixel; | 19 | } Pixel; |
| 16 | 20 | ||
| 17 | typedef enum TileDescType { | 21 | typedef enum TileDescType { |
| 18 | TileFromColour, | 22 | TileFromColour, |
| 19 | TileFromFile, | 23 | TileFromFile, |
| 20 | TileFromMemory | 24 | TileFromMemory, |
| 21 | } TileDescType; | 25 | } TileDescType; |
| 22 | 26 | ||
| 23 | typedef struct TileDesc { | 27 | typedef struct TileDesc { |
| 24 | TileDescType type; | 28 | TileDescType type; |
| 29 | int width; /// Tile width in pixels. | ||
| 30 | int height; /// Tile height in pixels. | ||
| 25 | union { | 31 | union { |
| 26 | Pixel colour; | 32 | Pixel colour; /// Constant colour tile. |
| 27 | struct { | 33 | struct { |
| 28 | const char* path; | 34 | const char* path; |
| 29 | } file; | 35 | } file; |
| 30 | struct { | 36 | struct { |
| 31 | const void* data; | 37 | const uint8_t* data; /// sizeof(Pixel) * width * height |
| 32 | } mem; | 38 | } mem; |
| 33 | }; | 39 | }; |
| 34 | } TileDesc; | 40 | } TileDesc; |
| 35 | 41 | ||
| 42 | typedef struct WorldDesc { | ||
| 43 | int tile_width; /// Base tile width in pixels. | ||
| 44 | int tile_height; /// Base tile height in pixels. | ||
| 45 | int world_width; /// World width in tiles. | ||
| 46 | int world_height; /// World height in tiles. | ||
| 47 | int max_num_tiles; /// 0 for an implementation-defined default. | ||
| 48 | } WorldDesc; | ||
| 49 | |||
| 36 | typedef struct IsoGfxDesc { | 50 | typedef struct IsoGfxDesc { |
| 37 | int screen_width; | 51 | int screen_width; /// Screen width in pixels. |
| 38 | int screen_height; | 52 | int screen_height; /// Screen height in pixels. |
| 39 | int tile_width; | ||
| 40 | int tile_height; | ||
| 41 | int world_width; | ||
| 42 | int world_height; | ||
| 43 | int max_num_tiles; // 0 for an implementation-defined default. | ||
| 44 | } IsoGfxDesc; | 53 | } IsoGfxDesc; |
| 45 | 54 | ||
| 55 | /// Create a new isometric graphics engine. | ||
| 46 | IsoGfx* isogfx_new(const IsoGfxDesc*); | 56 | IsoGfx* isogfx_new(const IsoGfxDesc*); |
| 47 | 57 | ||
| 58 | /// Destroy the isometric graphics engine. | ||
| 48 | void isogfx_del(IsoGfx**); | 59 | void isogfx_del(IsoGfx**); |
| 49 | 60 | ||
| 61 | /// Create an empty world. | ||
| 62 | bool isogfx_make_world(IsoGfx*, const WorldDesc*); | ||
| 63 | |||
| 64 | /// Load a world from a tile map (.TM) file. | ||
| 65 | bool isogfx_load_world(IsoGfx*, const char* filepath); | ||
| 66 | |||
| 67 | /// Return the world's width. | ||
| 68 | int isogfx_world_width(const IsoGfx*); | ||
| 69 | |||
| 70 | /// Return the world's height. | ||
| 71 | int isogfx_world_height(const IsoGfx*); | ||
| 72 | |||
| 73 | /// Create a new tile. | ||
| 50 | Tile isogfx_make_tile(IsoGfx*, const TileDesc*); | 74 | Tile isogfx_make_tile(IsoGfx*, const TileDesc*); |
| 51 | 75 | ||
| 76 | /// Set the tile at position (x,y). | ||
| 52 | void isogfx_set_tile(IsoGfx*, int x, int y, Tile); | 77 | void isogfx_set_tile(IsoGfx*, int x, int y, Tile); |
| 53 | 78 | ||
| 79 | /// Set the tiles in positions in the range (x0,y0) - (x1,y1). | ||
| 54 | void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); | 80 | void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); |
| 55 | 81 | ||
| 82 | /// Translate Cartesian to isometric coordinates. | ||
| 56 | void isogfx_pick_tile( | 83 | void isogfx_pick_tile( |
| 57 | const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso); | 84 | const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso); |
| 58 | 85 | ||
| 86 | /// Render the world. | ||
| 59 | void isogfx_render(IsoGfx*); | 87 | void isogfx_render(IsoGfx*); |
| 60 | 88 | ||
| 89 | /// Draw/overlay a tile at position (x,y). | ||
| 90 | /// | ||
| 91 | /// This function just renders a tile at position (x,y) and should be called | ||
| 92 | /// after isogfx_render() to obtain the correct result. To set the tile at | ||
| 93 | /// position (x,y) instead, use isogfx_set_tile(). | ||
| 61 | void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); | 94 | void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); |
| 62 | 95 | ||
| 96 | /// Return a pointer to the internal colour buffer. | ||
| 97 | /// | ||
| 98 | /// Call after each call to isogfx_render() to retrieve the render output. | ||
| 63 | const Pixel* isogfx_get_screen_buffer(const IsoGfx*); | 99 | const Pixel* isogfx_get_screen_buffer(const IsoGfx*); |
| 64 | |||
| 65 | int isogfx_world_width(const IsoGfx*); | ||
| 66 | 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 @@ | |||
| 1 | #include <isogfx/isogfx.h> | 1 | #include <isogfx/isogfx.h> |
| 2 | 2 | ||
| 3 | #include <filesystem.h> | ||
| 3 | #include <mempool.h> | 4 | #include <mempool.h> |
| 4 | 5 | ||
| 6 | #include <linux/limits.h> | ||
| 7 | |||
| 5 | #include <assert.h> | 8 | #include <assert.h> |
| 6 | #include <stdbool.h> | 9 | #include <stdbool.h> |
| 7 | #include <stdint.h> | 10 | #include <stdint.h> |
| 11 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | 12 | #include <stdlib.h> |
| 9 | #include <string.h> | 13 | #include <string.h> |
| 10 | 14 | ||
| 11 | /// Maximum number of tiles unless the user chooses a non-zero value. | 15 | /// Maximum number of tiles unless the user chooses a non-zero value. |
| 12 | #define DEFAULT_MAX_NUM_TILES 1024 | 16 | #define DEFAULT_MAX_NUM_TILES 1024 |
| 13 | 17 | ||
| 18 | // ----------------------------------------------------------------------------- | ||
| 19 | // Tile set (TS) and tile map (TM) file formats. | ||
| 20 | // ----------------------------------------------------------------------------- | ||
| 21 | |||
| 22 | /// Maximum length of path strings in .TS and .TM files. | ||
| 23 | #define MAX_PATH_LENGTH 128 | ||
| 24 | |||
| 25 | typedef struct Ts_Tile { | ||
| 26 | uint16_t width; /// Tile width in pixels. | ||
| 27 | uint16_t height; /// Tile height in pixels. | ||
| 28 | Pixel pixels[1]; /// Count: width * height. | ||
| 29 | } Ts_Tile; | ||
| 30 | |||
| 31 | typedef struct Ts_TileSet { | ||
| 32 | uint16_t num_tiles; | ||
| 33 | uint16_t max_tile_width; /// Maximum tile width in pixels. | ||
| 34 | uint16_t max_tile_height; /// Maximum tile height in pixels. | ||
| 35 | Ts_Tile tiles[1]; /// Count: num_tiles. | ||
| 36 | } Ts_TileSet; | ||
| 37 | |||
| 38 | typedef struct Tm_Layer { | ||
| 39 | union { | ||
| 40 | char tileset_path[MAX_PATH_LENGTH]; // Relative to the Tm_Map file. | ||
| 41 | }; | ||
| 42 | Tile tiles[1]; /// Count: world_width * world_height. | ||
| 43 | } Tm_Layer; | ||
| 44 | |||
| 45 | typedef struct Tm_Map { | ||
| 46 | uint16_t world_width; /// World width in number of tiles. | ||
| 47 | uint16_t world_height; /// World height in number of tiles. | ||
| 48 | uint16_t base_tile_width; | ||
| 49 | uint16_t base_tile_height; | ||
| 50 | uint16_t num_layers; | ||
| 51 | Tm_Layer layers[1]; // Count: num_layers. | ||
| 52 | } Tm_Map; | ||
| 53 | |||
| 54 | static inline const Tm_Layer* tm_map_get_next_layer( | ||
| 55 | const Tm_Map* map, const Tm_Layer* layer) { | ||
| 56 | assert(map); | ||
| 57 | assert(layer); | ||
| 58 | return (const Tm_Layer*)((const uint8_t*)layer + sizeof(Tm_Layer) + | ||
| 59 | ((map->world_width * map->world_height - 1) * | ||
| 60 | sizeof(Tile))); | ||
| 61 | } | ||
| 62 | |||
| 63 | static inline const Ts_Tile* ts_tileset_get_next_tile( | ||
| 64 | const Ts_TileSet* tileset, const Ts_Tile* tile) { | ||
| 65 | assert(tileset); | ||
| 66 | assert(tile); | ||
| 67 | return (const Ts_Tile*)((const uint8_t*)tile + sizeof(Ts_Tile) + | ||
| 68 | ((tile->width * tile->height - 1) * sizeof(Pixel))); | ||
| 69 | } | ||
| 70 | |||
| 71 | // ----------------------------------------------------------------------------- | ||
| 72 | // Renderer state. | ||
| 73 | // ----------------------------------------------------------------------------- | ||
| 74 | |||
| 75 | // typedef Ts_Tile TileData; | ||
| 76 | |||
| 14 | typedef struct TileData { | 77 | typedef struct TileData { |
| 15 | Pixel pixels[1]; // Dynamically allocated. | 78 | uint16_t width; |
| 79 | uint16_t height; | ||
| 80 | uint16_t num_blocks; // Number of pixel blocks in the pixels mempool. | ||
| 81 | uint16_t pixels_index; // Offset into the pixels mempool. | ||
| 16 | } TileData; | 82 | } TileData; |
| 17 | 83 | ||
| 18 | DEF_MEMPOOL_DYN(TilePool, TileData) | 84 | DEF_MEMPOOL_DYN(TilePool, TileData) |
| 85 | DEF_MEMPOOL_DYN(PixelPool, Pixel) | ||
| 19 | 86 | ||
| 20 | typedef struct IsoGfx { | 87 | typedef struct IsoGfx { |
| 21 | Tile* world; | 88 | int screen_width; |
| 22 | Pixel* screen; | 89 | int screen_height; |
| 23 | uint8_t* tile_mask; | 90 | int tile_width; |
| 24 | TilePool tiles; | 91 | int tile_height; |
| 25 | int screen_width; | 92 | int world_width; |
| 26 | int screen_height; | 93 | int world_height; |
| 27 | int tile_width; | 94 | Tile* world; |
| 28 | int tile_height; | 95 | Pixel* screen; |
| 29 | int world_width; | 96 | TilePool tiles; |
| 30 | int world_height; | 97 | PixelPool pixels; |
| 31 | int max_num_tiles; | ||
| 32 | } IsoGfx; | 98 | } IsoGfx; |
| 33 | 99 | ||
| 100 | // ----------------------------------------------------------------------------- | ||
| 101 | // Math and world / tile / screen access. | ||
| 102 | // ----------------------------------------------------------------------------- | ||
| 103 | |||
| 34 | typedef struct ivec2 { | 104 | typedef struct ivec2 { |
| 35 | int x, y; | 105 | int x, y; |
| 36 | } ivec2; | 106 | } ivec2; |
| @@ -70,38 +140,27 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { | |||
| 70 | .y = (-one_over_s * x + one_over_t * cart.y)}; | 140 | .y = (-one_over_s * x + one_over_t * cart.y)}; |
| 71 | } | 141 | } |
| 72 | 142 | ||
| 73 | Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { | 143 | static const Pixel* tile_xy_const_ref( |
| 144 | const IsoGfx* iso, const TileData* tile, int x, int y) { | ||
| 74 | assert(iso); | 145 | assert(iso); |
| 75 | assert(tile); | 146 | assert(tile); |
| 76 | assert(tile->pixels); | ||
| 77 | assert(x >= 0); | 147 | assert(x >= 0); |
| 78 | assert(y >= 0); | 148 | assert(y >= 0); |
| 79 | assert(x < iso->tile_width); | 149 | assert(x < tile->width); |
| 80 | assert(y < iso->tile_height); | 150 | assert(y < tile->height); |
| 81 | return &tile->pixels[y * iso->tile_width + x]; | 151 | return &mempool_get_block( |
| 152 | &iso->pixels, tile->pixels_index)[y * tile->width + x]; | ||
| 82 | } | 153 | } |
| 83 | 154 | ||
| 84 | Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { | 155 | static Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { |
| 85 | assert(iso); | 156 | return *tile_xy_const_ref(iso, tile, x, y); |
| 86 | assert(tile); | ||
| 87 | assert(tile->pixels); | ||
| 88 | assert(x >= 0); | ||
| 89 | assert(y >= 0); | ||
| 90 | assert(x < iso->tile_width); | ||
| 91 | assert(y < iso->tile_height); | ||
| 92 | return tile->pixels[y * iso->tile_width + x]; | ||
| 93 | } | 157 | } |
| 94 | 158 | ||
| 95 | static inline Tile world_xy(IsoGfx* iso, int x, int y) { | 159 | static Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { |
| 96 | assert(iso); | 160 | return (Pixel*)tile_xy_const_ref(iso, tile, x, y); |
| 97 | assert(x >= 0); | ||
| 98 | assert(y >= 0); | ||
| 99 | assert(x < iso->world_width); | ||
| 100 | assert(y < iso->world_height); | ||
| 101 | return iso->world[y * iso->world_width + x]; | ||
| 102 | } | 161 | } |
| 103 | 162 | ||
| 104 | static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { | 163 | static inline const Tile* world_xy_const_ref(const IsoGfx* iso, int x, int y) { |
| 105 | assert(iso); | 164 | assert(iso); |
| 106 | assert(x >= 0); | 165 | assert(x >= 0); |
| 107 | assert(y >= 0); | 166 | assert(y >= 0); |
| @@ -110,16 +169,16 @@ static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { | |||
| 110 | return &iso->world[y * iso->world_width + x]; | 169 | return &iso->world[y * iso->world_width + x]; |
| 111 | } | 170 | } |
| 112 | 171 | ||
| 113 | static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { | 172 | static inline Tile world_xy(const IsoGfx* iso, int x, int y) { |
| 114 | assert(iso); | 173 | return *world_xy_const_ref(iso, x, y); |
| 115 | assert(x >= 0); | ||
| 116 | assert(y >= 0); | ||
| 117 | assert(x < iso->screen_width); | ||
| 118 | assert(y < iso->screen_height); | ||
| 119 | return iso->screen[y * iso->screen_width + x]; | ||
| 120 | } | 174 | } |
| 121 | 175 | ||
| 122 | static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { | 176 | static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { |
| 177 | return (Tile*)world_xy_const_ref(iso, x, y); | ||
| 178 | } | ||
| 179 | |||
| 180 | static inline const Pixel* screen_xy_const_ref( | ||
| 181 | const IsoGfx* iso, int x, int y) { | ||
| 123 | assert(iso); | 182 | assert(iso); |
| 124 | assert(x >= 0); | 183 | assert(x >= 0); |
| 125 | assert(y >= 0); | 184 | assert(y >= 0); |
| @@ -128,169 +187,279 @@ static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { | |||
| 128 | return &iso->screen[y * iso->screen_width + x]; | 187 | return &iso->screen[y * iso->screen_width + x]; |
| 129 | } | 188 | } |
| 130 | 189 | ||
| 131 | static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { | 190 | static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { |
| 132 | assert(iso); | 191 | return *screen_xy_const_ref(iso, x, y); |
| 133 | |||
| 134 | const TileData* data = mempool_get_block(&iso->tiles, tile); | ||
| 135 | assert(data); | ||
| 136 | |||
| 137 | for (int py = 0; py < iso->tile_height; ++py) { | ||
| 138 | for (int px = 0; px < iso->tile_width; ++px) { | ||
| 139 | const Pixel colour = tile_xy(iso, data, px, py); | ||
| 140 | const int sx = origin.x + px; | ||
| 141 | const int sy = origin.y + py; | ||
| 142 | if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) && | ||
| 143 | (sy < iso->screen_height)) { | ||
| 144 | const uint8_t mask = iso->tile_mask[py * iso->tile_width + px]; | ||
| 145 | if (mask == 1) { | ||
| 146 | *screen_xy_mut(iso, sx, sy) = colour; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | } | 192 | } |
| 152 | 193 | ||
| 153 | static void draw(IsoGfx* iso) { | 194 | static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { |
| 154 | assert(iso); | 195 | return (Pixel*)screen_xy_const_ref(iso, x, y); |
| 155 | 196 | } | |
| 156 | const int W = iso->screen_width; | ||
| 157 | const int H = iso->screen_height; | ||
| 158 | 197 | ||
| 159 | memset(iso->screen, 0, W * H * sizeof(Pixel)); | 198 | // ----------------------------------------------------------------------------- |
| 199 | // Renderer, world and tile management. | ||
| 200 | // ----------------------------------------------------------------------------- | ||
| 160 | 201 | ||
| 161 | const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; | 202 | IsoGfx* isogfx_new(const IsoGfxDesc* desc) { |
| 162 | const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; | 203 | assert(desc->screen_width > 0); |
| 163 | const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; | 204 | assert(desc->screen_height > 0); |
| 205 | // Part of our implementation assumes even widths and heights for precision. | ||
| 206 | assert((desc->screen_width & 1) == 0); | ||
| 207 | assert((desc->screen_height & 1) == 0); | ||
| 164 | 208 | ||
| 165 | // TODO: Culling. | 209 | IsoGfx* iso = calloc(1, sizeof(IsoGfx)); |
| 166 | // Ex: map the screen corners to tile space to cull. | 210 | if (!iso) { |
| 167 | // Ex: walk in screen space and fetch the tile. | 211 | return 0; |
| 168 | // The tile-centric approach might be more cache-friendly, however, since the | ||
| 169 | // screen-centric approach would juggle multiple tiles throughout the scan. | ||
| 170 | for (int ty = 0; ty < iso->world_height; ++ty) { | ||
| 171 | for (int tx = 0; tx < iso->world_width; ++tx) { | ||
| 172 | const Tile tile = world_xy(iso, tx, ty); | ||
| 173 | const ivec2 so = | ||
| 174 | ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty))); | ||
| 175 | draw_tile(iso, so, tile); | ||
| 176 | } | ||
| 177 | } | 212 | } |
| 178 | } | ||
| 179 | |||
| 180 | /// Creates a tile mask procedurally. | ||
| 181 | static void make_tile_mask(IsoGfx* iso) { | ||
| 182 | assert(iso); | ||
| 183 | assert(iso->tile_mask); | ||
| 184 | 213 | ||
| 185 | for (int y = 0; y < iso->tile_height / 2; ++y) { | 214 | iso->screen_width = desc->screen_width; |
| 186 | const int mask_start = iso->tile_width / 2 - 2 * y - 1; | 215 | iso->screen_height = desc->screen_height; |
| 187 | const int mask_end = iso->tile_width / 2 + 2 * y + 1; | ||
| 188 | for (int x = 0; x < iso->tile_width; ++x) { | ||
| 189 | const bool masked = (mask_start <= x) && (x <= mask_end); | ||
| 190 | const uint8_t val = masked ? 1 : 0; | ||
| 191 | 216 | ||
| 192 | // Top half. | 217 | const int screen_size = desc->screen_width * desc->screen_height; |
| 193 | iso->tile_mask[y * iso->tile_width + x] = val; | ||
| 194 | 218 | ||
| 195 | // Bottom half reflects the top half. | 219 | if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { |
| 196 | const int y_reflected = iso->tile_height - y - 1; | 220 | goto cleanup; |
| 197 | iso->tile_mask[y_reflected * iso->tile_width + x] = val; | ||
| 198 | } | ||
| 199 | } | 221 | } |
| 222 | |||
| 223 | return iso; | ||
| 224 | |||
| 225 | cleanup: | ||
| 226 | isogfx_del(&iso); | ||
| 227 | return 0; | ||
| 200 | } | 228 | } |
| 201 | 229 | ||
| 202 | /// Creates a tile with a constant colour. | 230 | /// Destroy the world and its tile set. |
| 203 | static void make_tile_from_colour( | 231 | static void destroy_world(IsoGfx* iso) { |
| 204 | const IsoGfx* iso, Pixel colour, TileData* tile) { | ||
| 205 | assert(iso); | 232 | assert(iso); |
| 206 | assert(tile); | 233 | if (iso->world) { |
| 234 | free(iso->world); | ||
| 235 | iso->world = 0; | ||
| 236 | } | ||
| 237 | mempool_del(&iso->tiles); | ||
| 238 | mempool_del(&iso->pixels); | ||
| 239 | } | ||
| 207 | 240 | ||
| 208 | for (int y = 0; y < iso->tile_height; ++y) { | 241 | void isogfx_del(IsoGfx** pIso) { |
| 209 | for (int x = 0; x < iso->tile_width; ++x) { | 242 | assert(pIso); |
| 210 | *tile_xy_mut(iso, tile, x, y) = colour; | 243 | IsoGfx* iso = *pIso; |
| 244 | if (iso) { | ||
| 245 | destroy_world(iso); | ||
| 246 | if (iso->screen) { | ||
| 247 | free(iso->screen); | ||
| 248 | iso->screen = 0; | ||
| 211 | } | 249 | } |
| 250 | free(iso); | ||
| 251 | *pIso = 0; | ||
| 212 | } | 252 | } |
| 213 | } | 253 | } |
| 214 | 254 | ||
| 215 | IsoGfx* isogfx_new(const IsoGfxDesc* desc) { | 255 | bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { |
| 216 | assert(desc->screen_width > 0); | 256 | assert(iso); |
| 217 | assert(desc->screen_height > 0); | 257 | assert(desc); |
| 218 | assert(desc->tile_width > 0); | 258 | assert(desc->tile_width > 0); |
| 219 | assert(desc->tile_height > 0); | 259 | assert(desc->tile_height > 0); |
| 220 | // Part of our implementation assumes even widths and heights for greater | 260 | // Part of our implementation assumes even widths and heights for greater |
| 221 | // precision. | 261 | // precision. |
| 222 | assert((desc->screen_width & 1) == 0); | ||
| 223 | assert((desc->screen_height & 1) == 0); | ||
| 224 | assert((desc->tile_width & 1) == 0); | 262 | assert((desc->tile_width & 1) == 0); |
| 225 | assert((desc->tile_height & 1) == 0); | 263 | assert((desc->tile_height & 1) == 0); |
| 226 | 264 | ||
| 227 | IsoGfx* iso = calloc(1, sizeof(IsoGfx)); | 265 | // Handle recreation by destroying the previous world. |
| 228 | if (!iso) { | 266 | destroy_world(iso); |
| 229 | return 0; | ||
| 230 | } | ||
| 231 | |||
| 232 | iso->screen_width = desc->screen_width; | ||
| 233 | iso->screen_height = desc->screen_height; | ||
| 234 | iso->tile_width = desc->tile_width; | ||
| 235 | iso->tile_height = desc->tile_height; | ||
| 236 | iso->world_width = desc->world_width; | ||
| 237 | iso->world_height = desc->world_height; | ||
| 238 | iso->max_num_tiles = | ||
| 239 | desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES; | ||
| 240 | 267 | ||
| 241 | const int world_size = desc->world_width * desc->world_height; | 268 | iso->tile_width = desc->tile_width; |
| 242 | const int screen_size = desc->screen_width * desc->screen_height; | 269 | iso->tile_height = desc->tile_height; |
| 243 | const int tile_size = desc->tile_width * desc->tile_height; | 270 | iso->world_width = desc->world_width; |
| 271 | iso->world_height = desc->world_height; | ||
| 244 | 272 | ||
| 273 | const int world_size = desc->world_width * desc->world_height; | ||
| 274 | const int tile_size = desc->tile_width * desc->tile_height; | ||
| 245 | const int tile_size_bytes = tile_size * (int)sizeof(Pixel); | 275 | const int tile_size_bytes = tile_size * (int)sizeof(Pixel); |
| 276 | const int tile_pool_size = | ||
| 277 | desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES; | ||
| 246 | 278 | ||
| 247 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { | 279 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { |
| 248 | goto cleanup; | 280 | goto cleanup; |
| 249 | } | 281 | } |
| 250 | if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { | 282 | if (!mempool_make_dyn(&iso->tiles, tile_pool_size, tile_size_bytes)) { |
| 283 | goto cleanup; | ||
| 284 | } | ||
| 285 | |||
| 286 | return true; | ||
| 287 | |||
| 288 | cleanup: | ||
| 289 | destroy_world(iso); | ||
| 290 | mempool_del(&iso->tiles); | ||
| 291 | return false; | ||
| 292 | } | ||
| 293 | |||
| 294 | bool isogfx_load_world(IsoGfx* iso, const char* filepath) { | ||
| 295 | assert(iso); | ||
| 296 | assert(filepath); | ||
| 297 | |||
| 298 | bool success = false; | ||
| 299 | |||
| 300 | // Handle recreation by destroying the previous world. | ||
| 301 | destroy_world(iso); | ||
| 302 | |||
| 303 | // Load the map. | ||
| 304 | printf("Load tile map: %s\n", filepath); | ||
| 305 | Tm_Map* map = read_file(filepath); | ||
| 306 | if (!map) { | ||
| 251 | goto cleanup; | 307 | goto cleanup; |
| 252 | } | 308 | } |
| 253 | if (!(iso->tile_mask = calloc(tile_size, sizeof(uint8_t)))) { | 309 | |
| 310 | // Allocate memory for the map and tile sets. | ||
| 311 | const int world_size = map->world_width * map->world_height; | ||
| 312 | const int base_tile_size = map->base_tile_width * map->base_tile_height; | ||
| 313 | const int base_tile_size_bytes = base_tile_size * (int)sizeof(Pixel); | ||
| 314 | // TODO: Need to get the total number of tiles from the map. | ||
| 315 | const int tile_pool_size = DEFAULT_MAX_NUM_TILES; | ||
| 316 | |||
| 317 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { | ||
| 254 | goto cleanup; | 318 | goto cleanup; |
| 255 | } | 319 | } |
| 256 | if (!mempool_make_dyn(&iso->tiles, iso->max_num_tiles, tile_size_bytes)) { | 320 | if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) { |
| 321 | goto cleanup; | ||
| 322 | } | ||
| 323 | if (!mempool_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) { | ||
| 257 | goto cleanup; | 324 | goto cleanup; |
| 258 | } | 325 | } |
| 259 | 326 | ||
| 260 | make_tile_mask(iso); | 327 | // Load the tile sets. |
| 328 | const Tm_Layer* layer = &map->layers[0]; | ||
| 329 | // TODO: Handle num_layers layers. | ||
| 330 | for (int i = 0; i < 1; ++i) { | ||
| 331 | const char* ts_path = layer->tileset_path; | ||
| 332 | |||
| 333 | // Tile set path is relative to the tile map file. Make it relative to the | ||
| 334 | // current working directory before loading. | ||
| 335 | char ts_path_cwd[PATH_MAX] = {0}; | ||
| 336 | if (!make_relative_path(MAX_PATH_LENGTH, filepath, ts_path, ts_path_cwd)) { | ||
| 337 | goto cleanup; | ||
| 338 | } | ||
| 261 | 339 | ||
| 262 | return iso; | 340 | Ts_TileSet* tileset = read_file(ts_path_cwd); |
| 341 | if (!tileset) { | ||
| 342 | goto cleanup; | ||
| 343 | }; | ||
| 344 | |||
| 345 | // Load tile data. | ||
| 346 | const Ts_Tile* tile = &tileset->tiles[0]; | ||
| 347 | for (uint16_t j = 0; j < tileset->num_tiles; ++j) { | ||
| 348 | // Tile dimensions should be a multiple of the base tile size. | ||
| 349 | assert((tile->width % map->base_tile_width) == 0); | ||
| 350 | assert((tile->height % map->base_tile_height) == 0); | ||
| 351 | |||
| 352 | const uint16_t tile_size = tile->width * tile->height; | ||
| 353 | |||
| 354 | // TODO: Add function in mempool to alloc N consecutive blocks. | ||
| 355 | const int num_blocks = tile_size / base_tile_size; | ||
| 356 | Pixel* pixels = mempool_alloc(&iso->pixels); | ||
| 357 | assert(pixels); | ||
| 358 | // This is ugly and assumes that blocks are allocated consecutively. | ||
| 359 | for (int b = 1; b < num_blocks; ++b) { | ||
| 360 | Pixel* block = mempool_alloc(&iso->pixels); | ||
| 361 | assert(block); | ||
| 362 | } | ||
| 363 | memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel)); | ||
| 263 | 364 | ||
| 264 | cleanup: | 365 | TileData* tile_data = mempool_alloc(&iso->tiles); |
| 265 | isogfx_del(&iso); | 366 | assert(tile_data); |
| 266 | return 0; | 367 | tile_data->width = tile->width; |
| 267 | } | 368 | tile_data->height = tile->height; |
| 369 | tile_data->num_blocks = (uint16_t)num_blocks; | ||
| 370 | tile_data->pixels_index = | ||
| 371 | (uint16_t)mempool_get_block_index(&iso->pixels, pixels); | ||
| 268 | 372 | ||
| 269 | void isogfx_del(IsoGfx** pIso) { | 373 | tile = ts_tileset_get_next_tile(tileset, tile); |
| 270 | assert(pIso); | ||
| 271 | IsoGfx* iso = *pIso; | ||
| 272 | if (iso) { | ||
| 273 | if (iso->world) { | ||
| 274 | free(iso->world); | ||
| 275 | } | 374 | } |
| 276 | if (iso->screen) { | 375 | |
| 277 | free(iso->screen); | 376 | printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd); |
| 377 | |||
| 378 | free(tileset); | ||
| 379 | layer = tm_map_get_next_layer(map, layer); | ||
| 380 | } | ||
| 381 | |||
| 382 | // Load the map into the world. | ||
| 383 | layer = &map->layers[0]; | ||
| 384 | // TODO: Handle num_layers layers. | ||
| 385 | for (int i = 0; i < 1; ++i) { | ||
| 386 | memcpy(iso->world, layer->tiles, world_size * sizeof(Tile)); | ||
| 387 | |||
| 388 | // TODO: We need to handle 'firsgid' in TMX files. | ||
| 389 | for (int j = 0; j < world_size; ++j) { | ||
| 390 | iso->world[j] -= 1; | ||
| 278 | } | 391 | } |
| 279 | if (iso->tile_mask) { | 392 | |
| 280 | free(iso->tile_mask); | 393 | layer = tm_map_get_next_layer(map, layer); |
| 394 | } | ||
| 395 | |||
| 396 | iso->world_width = map->world_width; | ||
| 397 | iso->world_height = map->world_height; | ||
| 398 | iso->tile_width = map->base_tile_width; | ||
| 399 | iso->tile_height = map->base_tile_height; | ||
| 400 | |||
| 401 | success = true; | ||
| 402 | |||
| 403 | cleanup: | ||
| 404 | if (map) { | ||
| 405 | free(map); | ||
| 406 | } | ||
| 407 | if (!success) { | ||
| 408 | destroy_world(iso); | ||
| 409 | } | ||
| 410 | return success; | ||
| 411 | } | ||
| 412 | |||
| 413 | int isogfx_world_width(const IsoGfx* iso) { | ||
| 414 | assert(iso); | ||
| 415 | return iso->world_width; | ||
| 416 | } | ||
| 417 | |||
| 418 | int isogfx_world_height(const IsoGfx* iso) { | ||
| 419 | assert(iso); | ||
| 420 | return iso->world_height; | ||
| 421 | } | ||
| 422 | |||
| 423 | /// Create a tile mask procedurally. | ||
| 424 | static void make_tile_from_colour( | ||
| 425 | const IsoGfx* iso, Pixel colour, TileData* tile) { | ||
| 426 | assert(iso); | ||
| 427 | assert(tile); | ||
| 428 | |||
| 429 | const int width = tile->width; | ||
| 430 | const int height = tile->height; | ||
| 431 | const int r = width / height; | ||
| 432 | |||
| 433 | for (int y = 0; y < height / 2; ++y) { | ||
| 434 | const int mask_start = width / 2 - r * y - 1; | ||
| 435 | const int mask_end = width / 2 + r * y + 1; | ||
| 436 | for (int x = 0; x < width; ++x) { | ||
| 437 | const bool mask = (mask_start <= x) && (x <= mask_end); | ||
| 438 | const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; | ||
| 439 | |||
| 440 | // Top half. | ||
| 441 | *tile_xy_mut(iso, tile, x, y) = val; | ||
| 442 | |||
| 443 | // Bottom half reflects the top half. | ||
| 444 | const int y_reflected = height - y - 1; | ||
| 445 | *tile_xy_mut(iso, tile, x, y_reflected) = val; | ||
| 281 | } | 446 | } |
| 282 | mempool_del(&iso->tiles); | ||
| 283 | free(iso); | ||
| 284 | } | 447 | } |
| 285 | } | 448 | } |
| 286 | 449 | ||
| 287 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { | 450 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { |
| 288 | assert(iso); | 451 | assert(iso); |
| 289 | assert(desc); | 452 | assert(desc); |
| 453 | // Client must create world before creating tiles. | ||
| 454 | assert(iso->tile_width > 0); | ||
| 455 | assert(iso->tile_height > 0); | ||
| 290 | 456 | ||
| 291 | TileData* tile = mempool_alloc(&iso->tiles); | 457 | TileData* tile = mempool_alloc(&iso->tiles); |
| 292 | assert(tile); // TODO: Make this a hard assert. | 458 | assert(tile); // TODO: Make this a hard assert. |
| 293 | 459 | ||
| 460 | tile->width = desc->width; | ||
| 461 | tile->height = desc->height; | ||
| 462 | |||
| 294 | switch (desc->type) { | 463 | switch (desc->type) { |
| 295 | case TileFromColour: | 464 | case TileFromColour: |
| 296 | make_tile_from_colour(iso, desc->colour, tile); | 465 | make_tile_from_colour(iso, desc->colour, tile); |
| @@ -311,6 +480,88 @@ void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { | |||
| 311 | *world_xy_mut(iso, x, y) = tile; | 480 | *world_xy_mut(iso, x, y) = tile; |
| 312 | } | 481 | } |
| 313 | 482 | ||
| 483 | void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { | ||
| 484 | assert(iso); | ||
| 485 | for (int y = y0; y < y1; ++y) { | ||
| 486 | for (int x = x0; x < x1; ++x) { | ||
| 487 | isogfx_set_tile(iso, x, y, tile); | ||
| 488 | } | ||
| 489 | } | ||
| 490 | } | ||
| 491 | |||
| 492 | // ----------------------------------------------------------------------------- | ||
| 493 | // Rendering and picking. | ||
| 494 | // ----------------------------------------------------------------------------- | ||
| 495 | |||
| 496 | static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { | ||
| 497 | assert(iso); | ||
| 498 | |||
| 499 | const TileData* tile_data = mempool_get_block(&iso->tiles, tile); | ||
| 500 | assert(tile_data); | ||
| 501 | |||
| 502 | // Tile can exceed screen bounds, so we must clip it. | ||
| 503 | #define max(a, b) (a > b ? a : b) | ||
| 504 | const int py_offset = max(0, (int)tile_data->height - origin.y); | ||
| 505 | origin.y = max(0, origin.y - (int)tile_data->height); | ||
| 506 | |||
| 507 | // Clip along Y and X as we draw. | ||
| 508 | for (int py = py_offset; | ||
| 509 | (py < tile_data->height) && (origin.y + py < iso->screen_height); ++py) { | ||
| 510 | const int sy = origin.y + py - py_offset; | ||
| 511 | for (int px = 0; | ||
| 512 | (px < tile_data->width) && (origin.x + px < iso->screen_width); ++px) { | ||
| 513 | const Pixel colour = tile_xy(iso, tile_data, px, py); | ||
| 514 | if (colour.a > 0) { | ||
| 515 | const int sx = origin.x + px; | ||
| 516 | *screen_xy_mut(iso, sx, sy) = colour; | ||
| 517 | } | ||
| 518 | } | ||
| 519 | } | ||
| 520 | |||
| 521 | // for (int py = 0; py < tile_data->height; ++py) { | ||
| 522 | // for (int px = 0; px < tile_data->width; ++px) { | ||
| 523 | // const Pixel colour = tile_xy(iso, tile_data, px, py); | ||
| 524 | // if (colour.a > 0) { | ||
| 525 | // const int sx = origin.x + px; | ||
| 526 | // const int sy = origin.y + py; | ||
| 527 | // if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) && | ||
| 528 | // (sy < iso->screen_height)) { | ||
| 529 | // *screen_xy_mut(iso, sx, sy) = colour; | ||
| 530 | // } | ||
| 531 | // } | ||
| 532 | // } | ||
| 533 | // } | ||
| 534 | } | ||
| 535 | |||
| 536 | static void draw(IsoGfx* iso) { | ||
| 537 | assert(iso); | ||
| 538 | |||
| 539 | const int W = iso->screen_width; | ||
| 540 | const int H = iso->screen_height; | ||
| 541 | |||
| 542 | memset(iso->screen, 0, W * H * sizeof(Pixel)); | ||
| 543 | |||
| 544 | // const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; | ||
| 545 | const ivec2 o = { | ||
| 546 | (iso->screen_width / 2) - (iso->tile_width / 2), iso->tile_height}; | ||
| 547 | const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; | ||
| 548 | const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; | ||
| 549 | |||
| 550 | // TODO: Culling. | ||
| 551 | // Ex: map the screen corners to tile space to cull. | ||
| 552 | // Ex: walk in screen space and fetch the tile. | ||
| 553 | // The tile-centric approach might be more cache-friendly since the | ||
| 554 | // screen-centric approach would juggle multiple tiles throughout the scan. | ||
| 555 | for (int ty = 0; ty < iso->world_height; ++ty) { | ||
| 556 | for (int tx = 0; tx < iso->world_width; ++tx) { | ||
| 557 | const Tile tile = world_xy(iso, tx, ty); | ||
| 558 | const ivec2 so = | ||
| 559 | ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty))); | ||
| 560 | draw_tile(iso, so, tile); | ||
| 561 | } | ||
| 562 | } | ||
| 563 | } | ||
| 564 | |||
| 314 | void isogfx_pick_tile( | 565 | void isogfx_pick_tile( |
| 315 | const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { | 566 | const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { |
| 316 | assert(iso); | 567 | assert(iso); |
| @@ -356,13 +607,3 @@ const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) { | |||
| 356 | assert(iso); | 607 | assert(iso); |
| 357 | return iso->screen; | 608 | return iso->screen; |
| 358 | } | 609 | } |
| 359 | |||
| 360 | int isogfx_world_width(const IsoGfx* iso) { | ||
| 361 | assert(iso); | ||
| 362 | return iso->world_width; | ||
| 363 | } | ||
| 364 | |||
| 365 | int isogfx_world_height(const IsoGfx* iso) { | ||
| 366 | assert(iso); | ||
| 367 | return iso->world_height; | ||
| 368 | } | ||
