diff options
Diffstat (limited to 'gfx-iso/src')
| -rw-r--r-- | gfx-iso/src/isogfx.c | 561 |
1 files changed, 401 insertions, 160 deletions
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 | } | ||
