diff options
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rw-r--r-- | demos/checkerboard/checkerboard.c | 29 | ||||
| -rw-r--r-- | demos/isomap/isomap.c | 24 | ||||
| -rw-r--r-- | include/isogfx/asset.h | 149 | ||||
| -rw-r--r-- | include/isogfx/isogfx.h | 57 | ||||
| -rw-r--r-- | include/isogfx/types.h | 14 | ||||
| -rw-r--r-- | src/asset.c | 34 | ||||
| -rw-r--r-- | src/isogfx.c | 605 | ||||
| -rw-r--r-- | tools/mkasset.py | 53 |
9 files changed, 487 insertions, 482 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 42d2502..498adc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -9,6 +9,7 @@ set(CMAKE_C_EXTENSIONS Off) | |||
| 9 | # isogfx | 9 | # isogfx |
| 10 | 10 | ||
| 11 | add_library(isogfx | 11 | add_library(isogfx |
| 12 | src/asset.c | ||
| 12 | src/isogfx.c) | 13 | src/isogfx.c) |
| 13 | 14 | ||
| 14 | target_include_directories(isogfx PUBLIC | 15 | target_include_directories(isogfx PUBLIC |
| @@ -16,8 +17,7 @@ target_include_directories(isogfx PUBLIC | |||
| 16 | 17 | ||
| 17 | target_link_libraries(isogfx PUBLIC | 18 | target_link_libraries(isogfx PUBLIC |
| 18 | filesystem | 19 | filesystem |
| 19 | mem | 20 | memstack) |
| 20 | mempool) | ||
| 21 | 21 | ||
| 22 | target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) | 22 | target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) |
| 23 | 23 | ||
diff --git a/demos/checkerboard/checkerboard.c b/demos/checkerboard/checkerboard.c index 9d1791e..b408fc2 100644 --- a/demos/checkerboard/checkerboard.c +++ b/demos/checkerboard/checkerboard.c | |||
| @@ -20,6 +20,9 @@ static const int TILE_HEIGHT = TILE_WIDTH / 2; | |||
| 20 | static const int WORLD_WIDTH = 20; | 20 | static const int WORLD_WIDTH = 20; |
| 21 | static const int WORLD_HEIGHT = 20; | 21 | static const int WORLD_HEIGHT = 20; |
| 22 | 22 | ||
| 23 | #define MEMORY_SIZE (2 * 1024 * 1024) | ||
| 24 | uint8_t MEMORY[MEMORY_SIZE]; | ||
| 25 | |||
| 23 | static const TileDesc tile_set[] = { | 26 | static const TileDesc tile_set[] = { |
| 24 | {.type = TileFromColour, | 27 | {.type = TileFromColour, |
| 25 | .width = TILE_WIDTH, | 28 | .width = TILE_WIDTH, |
| @@ -35,6 +38,8 @@ static const TileDesc tile_set[] = { | |||
| 35 | .colour = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84, .a = 0xff}}, | 38 | .colour = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84, .a = 0xff}}, |
| 36 | }; | 39 | }; |
| 37 | 40 | ||
| 41 | #define NUM_TILES (sizeof(tile_set) / sizeof(tile_set[0])) | ||
| 42 | |||
| 38 | typedef enum Colour { | 43 | typedef enum Colour { |
| 39 | Black, | 44 | Black, |
| 40 | White, | 45 | White, |
| @@ -67,28 +72,28 @@ static bool init(GfxAppState* state, int argc, const char** argv) { | |||
| 67 | (void)argc; | 72 | (void)argc; |
| 68 | (void)argv; | 73 | (void)argv; |
| 69 | 74 | ||
| 70 | if (!(state->iso = isogfx_new(&(IsoGfxDesc){ | 75 | if (!((state->iso = |
| 71 | .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) { | 76 | isogfx_new(&(IsoGfxDesc){.memory = MEMORY, |
| 77 | .memory_size = MEMORY_SIZE, | ||
| 78 | .screen_width = SCREEN_WIDTH, | ||
| 79 | .screen_height = SCREEN_HEIGHT})))) { | ||
| 72 | return false; | 80 | return false; |
| 73 | } | 81 | } |
| 74 | IsoGfx* iso = state->iso; | 82 | IsoGfx* iso = state->iso; |
| 75 | 83 | ||
| 76 | isogfx_resize(iso, SCREEN_WIDTH, SCREEN_HEIGHT); | 84 | isogfx_make_world( |
| 77 | 85 | iso, &(WorldDesc){.tile_width = TILE_WIDTH, | |
| 78 | if (!isogfx_make_world( | 86 | .tile_height = TILE_HEIGHT, |
| 79 | iso, &(WorldDesc){.tile_width = TILE_WIDTH, | 87 | .world_width = WORLD_WIDTH, |
| 80 | .tile_height = TILE_HEIGHT, | 88 | .world_height = WORLD_HEIGHT, |
| 81 | .world_width = WORLD_WIDTH, | 89 | .num_tiles = NUM_TILES}); |
| 82 | .world_height = WORLD_HEIGHT})) { | ||
| 83 | return false; | ||
| 84 | } | ||
| 85 | 90 | ||
| 86 | const Tile black = isogfx_make_tile(iso, &tile_set[Black]); | 91 | const Tile black = isogfx_make_tile(iso, &tile_set[Black]); |
| 87 | const Tile white = isogfx_make_tile(iso, &tile_set[White]); | 92 | const Tile white = isogfx_make_tile(iso, &tile_set[White]); |
| 88 | state->red = isogfx_make_tile(iso, &tile_set[Red]); | 93 | state->red = isogfx_make_tile(iso, &tile_set[Red]); |
| 89 | make_checkerboard(iso, black, white); | 94 | make_checkerboard(iso, black, white); |
| 90 | 95 | ||
| 91 | if (!(state->backend = iso_backend_init(iso))) { | 96 | if (!((state->backend = iso_backend_init(iso)))) { |
| 92 | return false; | 97 | return false; |
| 93 | } | 98 | } |
| 94 | 99 | ||
diff --git a/demos/isomap/isomap.c b/demos/isomap/isomap.c index b328bfa..efae7fd 100644 --- a/demos/isomap/isomap.c +++ b/demos/isomap/isomap.c | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include <assert.h> | 6 | #include <assert.h> |
| 7 | #include <stdbool.h> | 7 | #include <stdbool.h> |
| 8 | #include <stdint.h> | ||
| 8 | 9 | ||
| 9 | static const int WINDOW_WIDTH = 1408; | 10 | static const int WINDOW_WIDTH = 1408; |
| 10 | static const int WINDOW_HEIGHT = 960; | 11 | static const int WINDOW_HEIGHT = 960; |
| @@ -14,6 +15,9 @@ static const int MAX_FPS = 60; | |||
| 14 | static const int SCREEN_WIDTH = 704; | 15 | static const int SCREEN_WIDTH = 704; |
| 15 | static const int SCREEN_HEIGHT = 480; | 16 | static const int SCREEN_HEIGHT = 480; |
| 16 | 17 | ||
| 18 | #define MEMORY_SIZE (2 * 1024 * 1024) | ||
| 19 | uint8_t MEMORY[MEMORY_SIZE]; | ||
| 20 | |||
| 17 | typedef struct GfxAppState { | 21 | typedef struct GfxAppState { |
| 18 | IsoBackend* backend; | 22 | IsoBackend* backend; |
| 19 | IsoGfx* iso; | 23 | IsoGfx* iso; |
| @@ -28,30 +32,30 @@ static bool init(GfxAppState* state, int argc, const char** argv) { | |||
| 28 | (void)argc; | 32 | (void)argc; |
| 29 | (void)argv; | 33 | (void)argv; |
| 30 | 34 | ||
| 31 | if (!(state->iso = isogfx_new(&(IsoGfxDesc){ | 35 | if (!((state->iso = |
| 32 | .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) { | 36 | isogfx_new(&(IsoGfxDesc){.memory = MEMORY, |
| 37 | .memory_size = MEMORY_SIZE, | ||
| 38 | .screen_width = SCREEN_WIDTH, | ||
| 39 | .screen_height = SCREEN_HEIGHT})))) { | ||
| 33 | return false; | 40 | return false; |
| 34 | } | 41 | } |
| 35 | IsoGfx* iso = state->iso; | 42 | IsoGfx* iso = state->iso; |
| 36 | 43 | ||
| 37 | isogfx_resize(iso, SCREEN_WIDTH, SCREEN_HEIGHT); | ||
| 38 | |||
| 39 | if (!isogfx_load_world(iso, "/home/jeanne/Nextcloud/assets/maps/demo-1.tm")) { | 44 | if (!isogfx_load_world(iso, "/home/jeanne/Nextcloud/assets/maps/demo-1.tm")) { |
| 40 | return false; | 45 | return false; |
| 41 | } | 46 | } |
| 42 | 47 | ||
| 43 | if (!isogfx_load_sprite_sheet( | 48 | if (!((state->stag_sheet = isogfx_load_sprite_sheet( |
| 44 | iso, | 49 | iso, |
| 45 | "/home/jeanne/Nextcloud/assets/tilesets/scrabling/critters/stag/" | 50 | "/home/jeanne/Nextcloud/assets/tilesets/scrabling/critters/stag/" |
| 46 | "stag.ss", | 51 | "stag.ss")))) { |
| 47 | &state->stag_sheet)) { | ||
| 48 | return false; | 52 | return false; |
| 49 | } | 53 | } |
| 50 | 54 | ||
| 51 | state->stag = isogfx_make_sprite(iso, state->stag_sheet); | 55 | state->stag = isogfx_make_sprite(iso, state->stag_sheet); |
| 52 | isogfx_set_sprite_position(iso, state->stag, 5, 4); | 56 | isogfx_set_sprite_position(iso, state->stag, 5, 4); |
| 53 | 57 | ||
| 54 | if (!(state->backend = iso_backend_init(iso))) { | 58 | if (!((state->backend = iso_backend_init(iso)))) { |
| 55 | return false; | 59 | return false; |
| 56 | } | 60 | } |
| 57 | 61 | ||
diff --git a/include/isogfx/asset.h b/include/isogfx/asset.h index 298c469..9aeb55d 100644 --- a/include/isogfx/asset.h +++ b/include/isogfx/asset.h | |||
| @@ -7,6 +7,8 @@ | |||
| 7 | */ | 7 | */ |
| 8 | #pragma once | 8 | #pragma once |
| 9 | 9 | ||
| 10 | #include <isogfx/types.h> | ||
| 11 | |||
| 10 | #include <assert.h> | 12 | #include <assert.h> |
| 11 | #include <stdint.h> | 13 | #include <stdint.h> |
| 12 | 14 | ||
| @@ -18,16 +20,16 @@ | |||
| 18 | // ----------------------------------------------------------------------------- | 20 | // ----------------------------------------------------------------------------- |
| 19 | 21 | ||
| 20 | typedef struct Ts_Tile { | 22 | typedef struct Ts_Tile { |
| 21 | uint16_t width; // Tile width in pixels. | 23 | uint16_t width; // Tile width in pixels. |
| 22 | uint16_t height; // Tile height in pixels. | 24 | uint16_t height; // Tile height in pixels. |
| 23 | Pixel pixels[1]; // Count: width * height. | 25 | uint32_t pixels; // Byte offset into the Ts_TileSet's 'pixels'. |
| 24 | } Ts_Tile; | 26 | } Ts_Tile; |
| 25 | 27 | ||
| 26 | typedef struct Ts_TileSet { | 28 | typedef struct Ts_TileSet { |
| 27 | uint16_t num_tiles; | 29 | uint16_t num_tiles; |
| 28 | uint16_t max_tile_width; // Maximum tile width in pixels. | 30 | uint16_t _pad; |
| 29 | uint16_t max_tile_height; // Maximum tile height in pixels. | 31 | Ts_Tile tiles[1]; // Count: num_tiles. |
| 30 | Ts_Tile tiles[1]; // Count: num_tiles. | 32 | Pixel pixels[]; // Count: sum_i(tile[i].width * tile[i].height). |
| 31 | } Ts_TileSet; | 33 | } Ts_TileSet; |
| 32 | 34 | ||
| 33 | // ----------------------------------------------------------------------------- | 35 | // ----------------------------------------------------------------------------- |
| @@ -35,19 +37,17 @@ typedef struct Ts_TileSet { | |||
| 35 | // ----------------------------------------------------------------------------- | 37 | // ----------------------------------------------------------------------------- |
| 36 | 38 | ||
| 37 | typedef struct Tm_Layer { | 39 | typedef struct Tm_Layer { |
| 38 | union { | ||
| 39 | char tileset_path[ISOGFX_MAX_PATH_LENGTH]; // Relative to the Tm_Map file. | ||
| 40 | }; | ||
| 41 | Tile tiles[1]; // Count: world_width * world_height. | 40 | Tile tiles[1]; // Count: world_width * world_height. |
| 42 | } Tm_Layer; | 41 | } Tm_Layer; |
| 43 | 42 | ||
| 44 | typedef struct Tm_Map { | 43 | typedef struct Tm_Map { |
| 44 | char tileset_path[ISOGFX_MAX_PATH_LENGTH]; // Relative to the Tm_Map file. | ||
| 45 | uint16_t world_width; // World width in number of tiles. | 45 | uint16_t world_width; // World width in number of tiles. |
| 46 | uint16_t world_height; // World height in number of tiles. | 46 | uint16_t world_height; // World height in number of tiles. |
| 47 | uint16_t base_tile_width; | 47 | uint16_t base_tile_width; |
| 48 | uint16_t base_tile_height; | 48 | uint16_t base_tile_height; |
| 49 | uint16_t num_layers; | 49 | uint16_t num_layers; |
| 50 | Tm_Layer layers[1]; // Count: num_layers. | 50 | Tm_Layer layers[]; // Count: num_layers. |
| 51 | } Tm_Map; | 51 | } Tm_Map; |
| 52 | 52 | ||
| 53 | // ----------------------------------------------------------------------------- | 53 | // ----------------------------------------------------------------------------- |
| @@ -88,28 +88,117 @@ typedef struct Ss_SpriteSheet { | |||
| 88 | // Data accessors. | 88 | // Data accessors. |
| 89 | // ----------------------------------------------------------------------------- | 89 | // ----------------------------------------------------------------------------- |
| 90 | 90 | ||
| 91 | /// Return the next tile in the tile set. | 91 | /// Return the tile set's pixels. |
| 92 | static inline const Ts_Tile* ts_tileset_get_next_tile( | 92 | static inline const Pixel* ts_tileset_get_pixels(const Ts_TileSet* tileset) { |
| 93 | const Ts_TileSet* tileset, const Ts_Tile* tile) { | 93 | assert(tileset); |
| 94 | return (const Pixel*)((const uint8_t*)&tileset->tiles[0] + | ||
| 95 | (tileset->num_tiles * sizeof(Ts_Tile))); | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Return the ith tile in the tile set. | ||
| 99 | static inline const Ts_Tile* ts_tileset_get_tile( | ||
| 100 | const Ts_TileSet* tileset, const int tile) { | ||
| 101 | assert(tileset); | ||
| 102 | assert(tile >= 0); | ||
| 103 | assert(tile < tileset->num_tiles); | ||
| 104 | return &tileset->tiles[tile]; | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Return the ith tile in the tile set. | ||
| 108 | static inline Ts_Tile* ts_tileset_get_tile_mut( | ||
| 109 | Ts_TileSet* tileset, const int tile) { | ||
| 110 | return (Ts_Tile*)ts_tileset_get_tile(tileset, tile); | ||
| 111 | } | ||
| 112 | |||
| 113 | /// Return the ith tile's pixels. | ||
| 114 | static inline const Pixel* ts_tileset_get_tile_pixels( | ||
| 115 | const Ts_TileSet* tileset, const int tile) { | ||
| 94 | assert(tileset); | 116 | assert(tileset); |
| 117 | assert(tile >= 0); | ||
| 118 | assert(tile < tileset->num_tiles); | ||
| 119 | const Pixel* pixels = ts_tileset_get_pixels(tileset); | ||
| 120 | const Ts_Tile* pTile = ts_tileset_get_tile(tileset, tile); | ||
| 121 | return (const Pixel*)((const uint8_t*)pixels + pTile->pixels); | ||
| 122 | } | ||
| 123 | |||
| 124 | /// Return the ith tile's pixels. | ||
| 125 | static inline Pixel* ts_tileset_get_tile_pixels_mut( | ||
| 126 | Ts_TileSet* tileset, const int tile) { | ||
| 127 | return (Pixel*)ts_tileset_get_tile_pixels(tileset, tile); | ||
| 128 | } | ||
| 129 | |||
| 130 | /// Return the ith tile's pixels. | ||
| 131 | static inline const Pixel* ts_tile_get_pixels( | ||
| 132 | const Pixel* pixels, const Ts_Tile* tile) { | ||
| 133 | assert(pixels); | ||
| 95 | assert(tile); | 134 | assert(tile); |
| 96 | return (const Ts_Tile*)((const uint8_t*)tile + sizeof(Ts_Tile) + | 135 | return (const Pixel*)((const uint8_t*)pixels + tile->pixels); |
| 97 | ((tile->width * tile->height - 1) * sizeof(Pixel))); | 136 | } |
| 137 | |||
| 138 | /// Return the tile's pixel at (x,y). | ||
| 139 | static const Pixel* ts_tile_xy( | ||
| 140 | const Pixel* tile_pixels, const Ts_Tile* tile, int x, int y) { | ||
| 141 | assert(tile_pixels); | ||
| 142 | assert(tile); | ||
| 143 | assert(x >= 0); | ||
| 144 | assert(y >= 0); | ||
| 145 | assert(x < tile->width); | ||
| 146 | assert(y < tile->height); | ||
| 147 | return &tile_pixels[y * tile->width + x]; | ||
| 148 | } | ||
| 149 | |||
| 150 | /// Return the tile's pixel at (x,y). | ||
| 151 | static Pixel* ts_tile_xy_mut( | ||
| 152 | const Pixel* pixels, const Ts_Tile* tile, int x, int y) { | ||
| 153 | return (Pixel*)ts_tile_xy(pixels, tile, x, y); | ||
| 98 | } | 154 | } |
| 99 | 155 | ||
| 100 | /// Return the next layer in the tile map. | 156 | /// Return the ith layer in the tile map. |
| 101 | static inline const Tm_Layer* tm_map_get_next_layer( | 157 | static inline const Tm_Layer* tm_map_get_layer( |
| 102 | const Tm_Map* map, const Tm_Layer* layer) { | 158 | const Tm_Map* map, const int layer) { |
| 103 | assert(map); | 159 | assert(map); |
| 104 | assert(layer); | 160 | assert(layer >= 0); |
| 105 | return (const Tm_Layer*)((const uint8_t*)layer + sizeof(Tm_Layer) + | 161 | assert(layer < map->num_layers); |
| 106 | ((map->world_width * map->world_height - 1) * | 162 | return (const Tm_Layer*)((const uint8_t*)map->layers + |
| 163 | (layer * map->world_width * map->world_height * | ||
| 107 | sizeof(Tile))); | 164 | sizeof(Tile))); |
| 108 | } | 165 | } |
| 109 | 166 | ||
| 167 | /// Return the ith layer in the tile map. | ||
| 168 | static inline Tm_Layer* tm_map_get_layer_mut(Tm_Map* map, const int layer) { | ||
| 169 | assert(map); | ||
| 170 | assert(layer >= 0); | ||
| 171 | assert(layer < map->num_layers); | ||
| 172 | return (Tm_Layer*)tm_map_get_layer(map, layer); | ||
| 173 | } | ||
| 174 | |||
| 175 | /// Return the tile in the layer. | ||
| 176 | static inline const Tile* tm_layer_get_tile_const_ref( | ||
| 177 | const Tm_Map* map, const Tm_Layer* layer, const int x, const int y) { | ||
| 178 | assert(map); | ||
| 179 | assert(layer); | ||
| 180 | assert(x >= 0); | ||
| 181 | assert(y >= 0); | ||
| 182 | assert(x < map->world_width); | ||
| 183 | assert(y < map->world_height); | ||
| 184 | return &layer->tiles[y * map->world_width + x]; | ||
| 185 | } | ||
| 186 | |||
| 187 | /// Return the tile in the layer. | ||
| 188 | static inline Tile tm_layer_get_tile( | ||
| 189 | const Tm_Map* map, const Tm_Layer* layer, const int x, const int y) { | ||
| 190 | return *tm_layer_get_tile_const_ref(map, layer, x, y); | ||
| 191 | } | ||
| 192 | |||
| 193 | /// Return the tile in the layer. | ||
| 194 | static inline Tile* tm_layer_get_tile_mut( | ||
| 195 | Tm_Map* map, Tm_Layer* layer, const int x, const int y) { | ||
| 196 | return (Tile*)tm_layer_get_tile_const_ref(map, layer, x, y); | ||
| 197 | } | ||
| 198 | |||
| 110 | /// Return the ith row in the sprite sheet. | 199 | /// Return the ith row in the sprite sheet. |
| 111 | static inline const Ss_Row* get_sprite_sheet_row( | 200 | static inline const Ss_Row* ss_get_sprite_sheet_row( |
| 112 | const Ss_SpriteSheet* sheet, int row) { | 201 | const Ss_SpriteSheet* sheet, const int row) { |
| 113 | assert(sheet); | 202 | assert(sheet); |
| 114 | assert(row >= 0); | 203 | assert(row >= 0); |
| 115 | assert(row < sheet->num_rows); | 204 | assert(row < sheet->num_rows); |
| @@ -120,8 +209,8 @@ static inline const Ss_Row* get_sprite_sheet_row( | |||
| 120 | } | 209 | } |
| 121 | 210 | ||
| 122 | /// Return the ith sprite in the row. | 211 | /// Return the ith sprite in the row. |
| 123 | static inline const uint8_t* get_sprite_sheet_sprite( | 212 | static inline const uint8_t* ss_get_sprite_sheet_sprite( |
| 124 | const Ss_SpriteSheet* sheet, const Ss_Row* row, int col) { | 213 | const Ss_SpriteSheet* sheet, const Ss_Row* row, const int col) { |
| 125 | assert(sheet); | 214 | assert(sheet); |
| 126 | assert(row); | 215 | assert(row); |
| 127 | assert(col >= 0); | 216 | assert(col >= 0); |
| @@ -130,3 +219,13 @@ static inline const uint8_t* get_sprite_sheet_sprite( | |||
| 130 | const uint8_t* sprite = &row->pixels[sprite_offset]; | 219 | const uint8_t* sprite = &row->pixels[sprite_offset]; |
| 131 | return sprite; | 220 | return sprite; |
| 132 | } | 221 | } |
| 222 | |||
| 223 | // ----------------------------------------------------------------------------- | ||
| 224 | // Validation. | ||
| 225 | // ----------------------------------------------------------------------------- | ||
| 226 | |||
| 227 | /// Validate the tile set. | ||
| 228 | bool ts_validate_tileset(const Ts_TileSet* tileset); | ||
| 229 | |||
| 230 | /// Validate the map. | ||
| 231 | bool tm_validate_map(const Tm_Map* map, const Ts_TileSet* tileset); | ||
diff --git a/include/isogfx/isogfx.h b/include/isogfx/isogfx.h index 3421a7b..93c6d4e 100644 --- a/include/isogfx/isogfx.h +++ b/include/isogfx/isogfx.h | |||
| @@ -3,26 +3,18 @@ | |||
| 3 | */ | 3 | */ |
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <stdbool.h> | 6 | #include <isogfx/types.h> |
| 7 | |||
| 8 | #include <stddef.h> | ||
| 7 | #include <stdint.h> | 9 | #include <stdint.h> |
| 8 | 10 | ||
| 9 | typedef struct IsoGfx IsoGfx; | 11 | typedef struct IsoGfx IsoGfx; |
| 10 | 12 | ||
| 11 | /// Sprite sheet handle. | 13 | /// Sprite sheet handle. |
| 12 | typedef uint16_t SpriteSheet; | 14 | typedef uintptr_t SpriteSheet; |
| 13 | 15 | ||
| 14 | /// Sprite handle. | 16 | /// Sprite handle. |
| 15 | typedef uint16_t Sprite; | 17 | typedef uintptr_t Sprite; |
| 16 | |||
| 17 | /// Tile handle. | ||
| 18 | typedef uint16_t Tile; | ||
| 19 | |||
| 20 | /// Colour channel. | ||
| 21 | typedef uint8_t Channel; | ||
| 22 | |||
| 23 | typedef struct Pixel { | ||
| 24 | Channel r, g, b, a; | ||
| 25 | } Pixel; | ||
| 26 | 18 | ||
| 27 | typedef enum TileDescType { | 19 | typedef enum TileDescType { |
| 28 | TileFromColour, | 20 | TileFromColour, |
| @@ -32,32 +24,32 @@ typedef enum TileDescType { | |||
| 32 | 24 | ||
| 33 | typedef struct TileDesc { | 25 | typedef struct TileDesc { |
| 34 | TileDescType type; | 26 | TileDescType type; |
| 35 | int width; /// Tile width in pixels. | 27 | int width; // Tile width in pixels. |
| 36 | int height; /// Tile height in pixels. | 28 | int height; // Tile height in pixels. |
| 37 | union { | 29 | union { |
| 38 | Pixel colour; /// Constant colour tile. | 30 | Pixel colour; // Constant colour tile. |
| 39 | struct { | 31 | struct { |
| 40 | const char* path; | 32 | const char* path; |
| 41 | } file; | 33 | } file; |
| 42 | struct { | 34 | struct { |
| 43 | const uint8_t* data; /// sizeof(Pixel) * width * height | 35 | const uint8_t* data; // sizeof(Pixel) * width * height |
| 44 | } mem; | 36 | } mem; |
| 45 | }; | 37 | }; |
| 46 | } TileDesc; | 38 | } TileDesc; |
| 47 | 39 | ||
| 48 | typedef struct WorldDesc { | 40 | typedef struct WorldDesc { |
| 49 | int tile_width; /// Base tile width in pixels. | 41 | int tile_width; // Base tile width in pixels. |
| 50 | int tile_height; /// Base tile height in pixels. | 42 | int tile_height; // Base tile height in pixels. |
| 51 | int world_width; /// World width in tiles. | 43 | int world_width; // World width in tiles. |
| 52 | int world_height; /// World height in tiles. | 44 | int world_height; // World height in tiles. |
| 53 | int max_num_tiles; /// 0 for an implementation-defined default. | 45 | int num_tiles; // Number of tiles to allocate memory for. |
| 54 | } WorldDesc; | 46 | } WorldDesc; |
| 55 | 47 | ||
| 56 | typedef struct IsoGfxDesc { | 48 | typedef struct IsoGfxDesc { |
| 57 | int screen_width; /// Screen width in pixels. | 49 | void* memory; // Block of memory for the engine to use. |
| 58 | int screen_height; /// Screen height in pixels. | 50 | size_t memory_size; // Size of memory block in bytes. |
| 59 | int max_num_sprites; /// 0 for an implementation-defined default. | 51 | int screen_width; // Screen width in pixels. |
| 60 | int sprite_sheet_pool_size_bytes; /// 0 for an implementation-defined default. | 52 | int screen_height; // Screen height in pixels. |
| 61 | } IsoGfxDesc; | 53 | } IsoGfxDesc; |
| 62 | 54 | ||
| 63 | /// Create a new isometric graphics engine. | 55 | /// Create a new isometric graphics engine. |
| @@ -66,8 +58,11 @@ IsoGfx* isogfx_new(const IsoGfxDesc*); | |||
| 66 | /// Destroy the isometric graphics engine. | 58 | /// Destroy the isometric graphics engine. |
| 67 | void isogfx_del(IsoGfx**); | 59 | void isogfx_del(IsoGfx**); |
| 68 | 60 | ||
| 61 | /// Clear all loaded worlds and sprites. | ||
| 62 | void isogfx_clear(IsoGfx*); | ||
| 63 | |||
| 69 | /// Create an empty world. | 64 | /// Create an empty world. |
| 70 | bool isogfx_make_world(IsoGfx*, const WorldDesc*); | 65 | void isogfx_make_world(IsoGfx*, const WorldDesc*); |
| 71 | 66 | ||
| 72 | /// Load a world from a tile map (.TM) file. | 67 | /// Load a world from a tile map (.TM) file. |
| 73 | bool isogfx_load_world(IsoGfx*, const char* filepath); | 68 | bool isogfx_load_world(IsoGfx*, const char* filepath); |
| @@ -88,14 +83,11 @@ void isogfx_set_tile(IsoGfx*, int x, int y, Tile); | |||
| 88 | void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); | 83 | void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); |
| 89 | 84 | ||
| 90 | /// Load a sprite sheet (.SS) file. | 85 | /// Load a sprite sheet (.SS) file. |
| 91 | bool isogfx_load_sprite_sheet(IsoGfx*, const char* filepath, SpriteSheet*); | 86 | SpriteSheet isogfx_load_sprite_sheet(IsoGfx*, const char* filepath); |
| 92 | 87 | ||
| 93 | /// Create an animated sprite. | 88 | /// Create an animated sprite. |
| 94 | Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet); | 89 | Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet); |
| 95 | 90 | ||
| 96 | /// Destroy the sprite. | ||
| 97 | void isogfx_del_sprite(IsoGfx*, Sprite); | ||
| 98 | |||
| 99 | /// Destroy all the sprites. | 91 | /// Destroy all the sprites. |
| 100 | void isogfx_del_sprites(IsoGfx*); | 92 | void isogfx_del_sprites(IsoGfx*); |
| 101 | 93 | ||
| @@ -120,9 +112,6 @@ void isogfx_render(IsoGfx*); | |||
| 120 | /// position (x,y) instead, use isogfx_set_tile(). | 112 | /// position (x,y) instead, use isogfx_set_tile(). |
| 121 | void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); | 113 | void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); |
| 122 | 114 | ||
| 123 | /// Resize the virtual screen's dimensions. | ||
| 124 | bool isogfx_resize(IsoGfx*, int screen_width, int screen_height); | ||
| 125 | |||
| 126 | /// Get the virtual screen's dimensions. | 115 | /// Get the virtual screen's dimensions. |
| 127 | void isogfx_get_screen_size(const IsoGfx*, int* width, int* height); | 116 | void isogfx_get_screen_size(const IsoGfx*, int* width, int* height); |
| 128 | 117 | ||
diff --git a/include/isogfx/types.h b/include/isogfx/types.h new file mode 100644 index 0000000..ce275dc --- /dev/null +++ b/include/isogfx/types.h | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <stdint.h> | ||
| 4 | |||
| 5 | /// Colour channel. | ||
| 6 | typedef uint8_t Channel; | ||
| 7 | |||
| 8 | /// Pixel. | ||
| 9 | typedef struct Pixel { | ||
| 10 | Channel r, g, b, a; | ||
| 11 | } Pixel; | ||
| 12 | |||
| 13 | /// Tile handle/index. | ||
| 14 | typedef uint16_t Tile; | ||
diff --git a/src/asset.c b/src/asset.c new file mode 100644 index 0000000..98ca083 --- /dev/null +++ b/src/asset.c | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | #include <isogfx/asset.h> | ||
| 2 | |||
| 3 | bool ts_validate_tileset(const Ts_TileSet* tileset) { | ||
| 4 | assert(tileset); | ||
| 5 | |||
| 6 | for (uint16_t i = 0; i < tileset->num_tiles; ++i) { | ||
| 7 | const Ts_Tile* tile = ts_tileset_get_tile(tileset, i); | ||
| 8 | // Tile should be non-empty. | ||
| 9 | if (tile->width == 0) { | ||
| 10 | return false; | ||
| 11 | } | ||
| 12 | if (tile->height == 0) { | ||
| 13 | return false; | ||
| 14 | } | ||
| 15 | } | ||
| 16 | return true; | ||
| 17 | } | ||
| 18 | |||
| 19 | bool tm_validate_map(const Tm_Map* map, const Ts_TileSet* tileset) { | ||
| 20 | assert(map); | ||
| 21 | assert(tileset); | ||
| 22 | |||
| 23 | for (uint16_t t = 0; t < tileset->num_tiles; ++t) { | ||
| 24 | const Ts_Tile* tile = ts_tileset_get_tile(tileset, t); | ||
| 25 | // Tile dimensions should be a multiple of the base tile size. | ||
| 26 | if ((tile->width % map->base_tile_width) != 0) { | ||
| 27 | return false; | ||
| 28 | } | ||
| 29 | if ((tile->height % map->base_tile_height) != 0) { | ||
| 30 | return false; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | return true; | ||
| 34 | } | ||
diff --git a/src/isogfx.c b/src/isogfx.c index 4dff67b..16760ac 100644 --- a/src/isogfx.c +++ b/src/isogfx.c | |||
| @@ -3,27 +3,16 @@ | |||
| 3 | #include <isogfx/asset.h> | 3 | #include <isogfx/asset.h> |
| 4 | 4 | ||
| 5 | #include <filesystem.h> | 5 | #include <filesystem.h> |
| 6 | #include <mem.h> | 6 | #include <memstack.h> |
| 7 | #include <mempool.h> | ||
| 8 | #include <path.h> | 7 | #include <path.h> |
| 9 | 8 | ||
| 10 | #include <linux/limits.h> | ||
| 11 | |||
| 12 | #include <assert.h> | 9 | #include <assert.h> |
| 13 | #include <stdbool.h> | ||
| 14 | #include <stdint.h> | 10 | #include <stdint.h> |
| 15 | #include <stdio.h> | 11 | #include <stdio.h> |
| 16 | #include <stdlib.h> | ||
| 17 | #include <string.h> | 12 | #include <string.h> |
| 18 | 13 | ||
| 19 | /// Maximum number of tiles unless the user specifies a value. | 14 | /// Maximum path length. |
| 20 | #define DEFAULT_MAX_NUM_TILES 1024 | 15 | #define MAX_PATH 256 |
| 21 | |||
| 22 | /// Maximum number of sprites unless the user specifies a value. | ||
| 23 | #define DEFAULT_MAX_NUM_SPRITES 128 | ||
| 24 | |||
| 25 | /// Size of sprite sheet pool in bytes unless the user specifies a value. | ||
| 26 | #define DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES (8 * 1024 * 1024) | ||
| 27 | 16 | ||
| 28 | /// Default animation speed. | 17 | /// Default animation speed. |
| 29 | #define ANIMATION_FPS 10 | 18 | #define ANIMATION_FPS 10 |
| @@ -43,41 +32,27 @@ typedef struct vec2 { | |||
| 43 | // Renderer state. | 32 | // Renderer state. |
| 44 | // ----------------------------------------------------------------------------- | 33 | // ----------------------------------------------------------------------------- |
| 45 | 34 | ||
| 46 | typedef struct TileData { | 35 | // TODO: Define a struct Screen with width, height and pixels. |
| 47 | uint16_t width; | ||
| 48 | uint16_t height; | ||
| 49 | uint16_t pixels_handle; // Handle to the tile's pixels in the pixel pool. | ||
| 50 | } TileData; | ||
| 51 | |||
| 52 | typedef struct SpriteData { | ||
| 53 | SpriteSheet sheet; // Handle to the sprite's sheet. | ||
| 54 | ivec2 position; | ||
| 55 | int animation; // Current animation. | ||
| 56 | int frame; // Current frame of animation. | ||
| 57 | } SpriteData; | ||
| 58 | 36 | ||
| 59 | DEF_MEMPOOL_DYN(TilePool, TileData) | 37 | typedef struct SpriteInstance { |
| 60 | DEF_MEM_DYN(PixelPool, Pixel) | 38 | struct SpriteInstance* next; |
| 61 | 39 | const Ss_SpriteSheet* sheet; | |
| 62 | DEF_MEMPOOL_DYN(SpritePool, SpriteData) | 40 | ivec2 position; |
| 63 | DEF_MEM_DYN(SpriteSheetPool, Ss_SpriteSheet) | 41 | int animation; // Current animation. |
| 42 | int frame; // Current frame of animation. | ||
| 43 | } SpriteInstance; | ||
| 64 | 44 | ||
| 65 | typedef struct IsoGfx { | 45 | typedef struct IsoGfx { |
| 66 | int screen_width; | 46 | int screen_width; |
| 67 | int screen_height; | 47 | int screen_height; |
| 68 | int tile_width; | ||
| 69 | int tile_height; | ||
| 70 | int world_width; | ||
| 71 | int world_height; | ||
| 72 | int max_num_sprites; | ||
| 73 | int sprite_sheet_pool_size_bytes; | ||
| 74 | double last_animation_time; | 48 | double last_animation_time; |
| 75 | Tile* world; | 49 | Tile next_tile; // For procedurally-generated tiles. |
| 76 | Pixel* screen; | 50 | Pixel* screen; |
| 77 | TilePool tiles; | 51 | Tm_Map* map; |
| 78 | PixelPool pixels; | 52 | Ts_TileSet* tileset; |
| 79 | SpritePool sprites; | 53 | SpriteInstance* head_sprite; // Head of sprites list. |
| 80 | SpriteSheetPool sheets; | 54 | memstack stack; |
| 55 | size_t watermark; | ||
| 81 | } IsoGfx; | 56 | } IsoGfx; |
| 82 | 57 | ||
| 83 | // ----------------------------------------------------------------------------- | 58 | // ----------------------------------------------------------------------------- |
| @@ -114,42 +89,6 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { | |||
| 114 | .y = (-one_over_s * x + one_over_t * cart.y)}; | 89 | .y = (-one_over_s * x + one_over_t * cart.y)}; |
| 115 | } | 90 | } |
| 116 | 91 | ||
| 117 | static const Pixel* tile_xy_const_ref( | ||
| 118 | const IsoGfx* iso, const TileData* tile, int x, int y) { | ||
| 119 | assert(iso); | ||
| 120 | assert(tile); | ||
| 121 | assert(x >= 0); | ||
| 122 | assert(y >= 0); | ||
| 123 | assert(x < tile->width); | ||
| 124 | assert(y < tile->height); | ||
| 125 | return &mem_get_chunk(&iso->pixels, tile->pixels_handle)[y * tile->width + x]; | ||
| 126 | } | ||
| 127 | |||
| 128 | // static Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { | ||
| 129 | // return *tile_xy_const_ref(iso, tile, x, y); | ||
| 130 | // } | ||
| 131 | |||
| 132 | static Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { | ||
| 133 | return (Pixel*)tile_xy_const_ref(iso, tile, x, y); | ||
| 134 | } | ||
| 135 | |||
| 136 | static inline const Tile* world_xy_const_ref(const IsoGfx* iso, int x, int y) { | ||
| 137 | assert(iso); | ||
| 138 | assert(x >= 0); | ||
| 139 | assert(y >= 0); | ||
| 140 | assert(x < iso->world_width); | ||
| 141 | assert(y < iso->world_height); | ||
| 142 | return &iso->world[y * iso->world_width + x]; | ||
| 143 | } | ||
| 144 | |||
| 145 | static inline Tile world_xy(const IsoGfx* iso, int x, int y) { | ||
| 146 | return *world_xy_const_ref(iso, x, y); | ||
| 147 | } | ||
| 148 | |||
| 149 | static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { | ||
| 150 | return (Tile*)world_xy_const_ref(iso, x, y); | ||
| 151 | } | ||
| 152 | |||
| 153 | static inline const Pixel* screen_xy_const_ref( | 92 | static inline const Pixel* screen_xy_const_ref( |
| 154 | const IsoGfx* iso, int x, int y) { | 93 | const IsoGfx* iso, int x, int y) { |
| 155 | assert(iso); | 94 | assert(iso); |
| @@ -168,15 +107,6 @@ static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { | |||
| 168 | return (Pixel*)screen_xy_const_ref(iso, x, y); | 107 | return (Pixel*)screen_xy_const_ref(iso, x, y); |
| 169 | } | 108 | } |
| 170 | 109 | ||
| 171 | static int calc_num_tile_blocks( | ||
| 172 | int base_tile_width, int base_tile_height, int tile_width, | ||
| 173 | int tile_height) { | ||
| 174 | const int base_tile_size = base_tile_width * base_tile_height; | ||
| 175 | const int tile_size = tile_width * tile_height; | ||
| 176 | const int num_blocks = tile_size / base_tile_size; | ||
| 177 | return num_blocks; | ||
| 178 | } | ||
| 179 | |||
| 180 | // ----------------------------------------------------------------------------- | 110 | // ----------------------------------------------------------------------------- |
| 181 | // Renderer, world and tile management. | 111 | // Renderer, world and tile management. |
| 182 | // ----------------------------------------------------------------------------- | 112 | // ----------------------------------------------------------------------------- |
| @@ -188,68 +118,54 @@ IsoGfx* isogfx_new(const IsoGfxDesc* desc) { | |||
| 188 | assert((desc->screen_width & 1) == 0); | 118 | assert((desc->screen_width & 1) == 0); |
| 189 | assert((desc->screen_height & 1) == 0); | 119 | assert((desc->screen_height & 1) == 0); |
| 190 | 120 | ||
| 191 | IsoGfx* iso = calloc(1, sizeof(IsoGfx)); | 121 | IsoGfx tmp = {0}; |
| 192 | if (!iso) { | 122 | if (!memstack_make(&tmp.stack, desc->memory_size, desc->memory)) { |
| 193 | return 0; | 123 | goto cleanup; |
| 194 | } | 124 | } |
| 125 | IsoGfx* iso = | ||
| 126 | memstack_alloc_aligned(&tmp.stack, sizeof(IsoGfx), alignof(IsoGfx)); | ||
| 127 | *iso = tmp; | ||
| 195 | 128 | ||
| 196 | iso->screen_width = desc->screen_width; | 129 | iso->screen_width = desc->screen_width; |
| 197 | iso->screen_height = desc->screen_height; | 130 | iso->screen_height = desc->screen_height; |
| 198 | |||
| 199 | iso->last_animation_time = 0.0; | 131 | iso->last_animation_time = 0.0; |
| 200 | 132 | ||
| 201 | iso->max_num_sprites = desc->max_num_sprites == 0 ? DEFAULT_MAX_NUM_SPRITES | 133 | const size_t screen_size_bytes = |
| 202 | : desc->max_num_sprites; | 134 | desc->screen_width * desc->screen_height * sizeof(Pixel); |
| 203 | iso->sprite_sheet_pool_size_bytes = desc->sprite_sheet_pool_size_bytes == 0 | 135 | iso->screen = |
| 204 | ? DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES | 136 | memstack_alloc_aligned(&iso->stack, screen_size_bytes, alignof(Pixel)); |
| 205 | : desc->sprite_sheet_pool_size_bytes; | ||
| 206 | 137 | ||
| 207 | const int screen_size = desc->screen_width * desc->screen_height; | 138 | iso->watermark = memstack_get_watermark(&iso->stack); |
| 208 | if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { | ||
| 209 | goto cleanup; | ||
| 210 | } | ||
| 211 | 139 | ||
| 212 | return iso; | 140 | return iso; |
| 213 | 141 | ||
| 214 | cleanup: | 142 | cleanup: |
| 215 | isogfx_del(&iso); | 143 | isogfx_del(&iso); |
| 216 | return 0; | 144 | return nullptr; |
| 217 | } | 145 | } |
| 218 | 146 | ||
| 219 | /// Destroy the world, its tile set, and the underlying pools. | 147 | void isogfx_clear(IsoGfx* iso) { |
| 220 | static void destroy_world(IsoGfx* iso) { | ||
| 221 | assert(iso); | 148 | assert(iso); |
| 222 | if (iso->world) { | 149 | iso->last_animation_time = 0.0; |
| 223 | free(iso->world); | 150 | iso->next_tile = 0; |
| 224 | iso->world = 0; | 151 | iso->map = nullptr; |
| 225 | } | 152 | iso->tileset = nullptr; |
| 226 | mempool_del(&iso->tiles); | 153 | iso->head_sprite = nullptr; |
| 227 | mem_del(&iso->pixels); | 154 | // The base of the stack contains the IsoGfx and the screen buffer. Make sure |
| 228 | } | 155 | // we don't clear them. |
| 229 | 156 | memstack_set_watermark(&iso->stack, iso->watermark); | |
| 230 | /// Destroy all loaded sprites and the underlying pools. | 157 | } |
| 231 | static void destroy_sprites(IsoGfx* iso) { | 158 | |
| 232 | assert(iso); | 159 | void isogfx_del(IsoGfx** ppIso) { |
| 233 | mempool_del(&iso->sprites); | 160 | assert(ppIso); |
| 234 | mem_del(&iso->sheets); | 161 | IsoGfx* iso = *ppIso; |
| 235 | } | ||
| 236 | |||
| 237 | void isogfx_del(IsoGfx** pIso) { | ||
| 238 | assert(pIso); | ||
| 239 | IsoGfx* iso = *pIso; | ||
| 240 | if (iso) { | 162 | if (iso) { |
| 241 | destroy_world(iso); | 163 | memstack_del(&iso->stack); |
| 242 | destroy_sprites(iso); | 164 | *ppIso = nullptr; |
| 243 | if (iso->screen) { | ||
| 244 | free(iso->screen); | ||
| 245 | iso->screen = 0; | ||
| 246 | } | ||
| 247 | free(iso); | ||
| 248 | *pIso = 0; | ||
| 249 | } | 165 | } |
| 250 | } | 166 | } |
| 251 | 167 | ||
| 252 | bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { | 168 | void isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { |
| 253 | assert(iso); | 169 | assert(iso); |
| 254 | assert(desc); | 170 | assert(desc); |
| 255 | assert(desc->tile_width > 0); | 171 | assert(desc->tile_width > 0); |
| @@ -258,36 +174,41 @@ bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { | |||
| 258 | // precision. | 174 | // precision. |
| 259 | assert((desc->tile_width & 1) == 0); | 175 | assert((desc->tile_width & 1) == 0); |
| 260 | assert((desc->tile_height & 1) == 0); | 176 | assert((desc->tile_height & 1) == 0); |
| 261 | 177 | // World must be non-empty. | |
| 262 | // Handle recreation by destroying the previous world. | 178 | assert(desc->world_width > 0); |
| 263 | destroy_world(iso); | 179 | assert(desc->world_height > 0); |
| 264 | 180 | // Must have >0 tiles. | |
| 265 | iso->tile_width = desc->tile_width; | 181 | assert(desc->num_tiles > 0); |
| 266 | iso->tile_height = desc->tile_height; | 182 | |
| 267 | iso->world_width = desc->world_width; | 183 | // Handle recreation by destroying the previous world and sprites. |
| 268 | iso->world_height = desc->world_height; | 184 | isogfx_clear(iso); |
| 269 | 185 | ||
| 270 | const int world_size = desc->world_width * desc->world_height; | 186 | const int world_size = desc->world_width * desc->world_height; |
| 271 | const int tile_size = desc->tile_width * desc->tile_height; | 187 | const size_t map_size_bytes = sizeof(Tm_Map) + (world_size * sizeof(Tile)); |
| 272 | const int tile_size_bytes = tile_size * (int)sizeof(Pixel); | 188 | |
| 273 | const int tile_pool_size = | 189 | // This implies that all tiles are of the base tile dimensions. |
| 274 | desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES; | 190 | // We could enhance the API to allow for supertiles as well. Take in max tile |
| 275 | 191 | // width and height and allocate enough space using those values. | |
| 276 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { | 192 | const size_t tile_size = desc->tile_width * desc->tile_height; |
| 277 | goto cleanup; | 193 | const size_t tile_size_bytes = tile_size * sizeof(Pixel); |
| 278 | } | 194 | const size_t tile_data_size_bytes = desc->num_tiles * tile_size_bytes; |
| 279 | if (!mempool_make_dyn(&iso->tiles, world_size, sizeof(TileData))) { | 195 | const size_t tileset_size_bytes = sizeof(Ts_TileSet) + |
| 280 | goto cleanup; | 196 | (desc->num_tiles * sizeof(Ts_Tile)) + |
| 281 | } | 197 | tile_data_size_bytes; |
| 282 | if (!mem_make_dyn(&iso->pixels, tile_pool_size, tile_size_bytes)) { | 198 | |
| 283 | goto cleanup; | 199 | iso->map = memstack_alloc_aligned(&iso->stack, map_size_bytes, 4); |
| 284 | } | 200 | *iso->map = (Tm_Map){ |
| 285 | 201 | .world_width = desc->world_width, | |
| 286 | return true; | 202 | .world_height = desc->world_height, |
| 287 | 203 | .base_tile_width = desc->tile_width, | |
| 288 | cleanup: | 204 | .base_tile_height = desc->tile_height, |
| 289 | destroy_world(iso); | 205 | .num_layers = 1, |
| 290 | return false; | 206 | }; |
| 207 | |||
| 208 | iso->tileset = memstack_alloc_aligned(&iso->stack, tileset_size_bytes, 4); | ||
| 209 | *iso->tileset = (Ts_TileSet){ | ||
| 210 | .num_tiles = desc->num_tiles, | ||
| 211 | }; | ||
| 291 | } | 212 | } |
| 292 | 213 | ||
| 293 | bool isogfx_load_world(IsoGfx* iso, const char* filepath) { | 214 | bool isogfx_load_world(IsoGfx* iso, const char* filepath) { |
| @@ -296,135 +217,74 @@ bool isogfx_load_world(IsoGfx* iso, const char* filepath) { | |||
| 296 | 217 | ||
| 297 | bool success = false; | 218 | bool success = false; |
| 298 | 219 | ||
| 299 | // Handle recreation by destroying the previous world. | 220 | // Handle recreation by destroying the previous world and sprites. |
| 300 | destroy_world(iso); | 221 | isogfx_clear(iso); |
| 301 | 222 | ||
| 302 | // Load the map. | 223 | // Load the map. |
| 303 | printf("Load tile map: %s\n", filepath); | 224 | printf("Load tile map: %s\n", filepath); |
| 304 | Tm_Map* map = read_file(filepath); | 225 | WITH_FILE(filepath, { |
| 305 | if (!map) { | 226 | const size_t map_size = get_file_size_f(file); |
| 227 | iso->map = memstack_alloc_aligned(&iso->stack, map_size, 4); | ||
| 228 | success = read_file_f(file, iso->map); | ||
| 229 | }); | ||
| 230 | if (!success) { | ||
| 306 | goto cleanup; | 231 | goto cleanup; |
| 307 | } | 232 | } |
| 233 | Tm_Map* const map = iso->map; | ||
| 308 | 234 | ||
| 309 | // Allocate memory for the map and tile sets. | 235 | // Load the tile set. |
| 310 | const int world_size = map->world_width * map->world_height; | 236 | // |
| 311 | const int base_tile_size = map->base_tile_width * map->base_tile_height; | 237 | // Tile set path is relative to the tile map file. Make it relative to the |
| 312 | const int base_tile_size_bytes = base_tile_size * (int)sizeof(Pixel); | 238 | // current working directory before loading. |
| 313 | // TODO: Need to get the total number of tiles from the map. | 239 | const char* ts_path = map->tileset_path; |
| 314 | const int tile_pool_size = DEFAULT_MAX_NUM_TILES; | 240 | char ts_path_cwd[MAX_PATH] = {0}; |
| 315 | 241 | if (!path_make_relative(filepath, ts_path, ts_path_cwd, MAX_PATH)) { | |
| 316 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { | ||
| 317 | goto cleanup; | ||
| 318 | } | ||
| 319 | if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) { | ||
| 320 | goto cleanup; | 242 | goto cleanup; |
| 321 | } | 243 | } |
| 322 | if (!mem_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) { | 244 | printf("Load tile set: %s\n", ts_path_cwd); |
| 245 | WITH_FILE(ts_path_cwd, { | ||
| 246 | const size_t file_size = get_file_size_f(file); | ||
| 247 | iso->tileset = memstack_alloc_aligned(&iso->stack, file_size, 4); | ||
| 248 | success = read_file_f(file, iso->tileset); | ||
| 249 | }); | ||
| 250 | if (!success) { | ||
| 251 | // TODO: Log errors using the log library. | ||
| 323 | goto cleanup; | 252 | goto cleanup; |
| 324 | } | 253 | } |
| 254 | const Ts_TileSet* const tileset = iso->tileset; | ||
| 255 | printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd); | ||
| 325 | 256 | ||
| 326 | // Load the tile sets. | 257 | // TODO: These assertions on input data should be library runtime errors. |
| 327 | const Tm_Layer* layer = &map->layers[0]; | 258 | assert(ts_validate_tileset(tileset)); |
| 328 | // TODO: Handle num_layers layers. | 259 | assert(tm_validate_map(map, tileset)); |
| 329 | for (int i = 0; i < 1; ++i) { | ||
| 330 | const char* ts_path = layer->tileset_path; | ||
| 331 | |||
| 332 | // Tile set path is relative to the tile map file. Make it relative to the | ||
| 333 | // current working directory before loading. | ||
| 334 | char ts_path_cwd[PATH_MAX] = {0}; | ||
| 335 | if (!path_make_relative(filepath, ts_path, ts_path_cwd, PATH_MAX)) { | ||
| 336 | goto cleanup; | ||
| 337 | } | ||
| 338 | |||
| 339 | Ts_TileSet* tileset = read_file(ts_path_cwd); | ||
| 340 | if (!tileset) { | ||
| 341 | // TODO: Log errors using the log library. | ||
| 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 | // TODO: These checks should be library runtime errors. | ||
| 349 | // Tile dimensions should be a multiple of the base tile size. | ||
| 350 | assert((tile->width % map->base_tile_width) == 0); | ||
| 351 | assert((tile->height % map->base_tile_height) == 0); | ||
| 352 | |||
| 353 | // Allocate N base tile size blocks for the tile. | ||
| 354 | const uint16_t tile_size = tile->width * tile->height; | ||
| 355 | const int num_blocks = tile_size / base_tile_size; | ||
| 356 | Pixel* pixels = mem_alloc(&iso->pixels, num_blocks); | ||
| 357 | assert(pixels); | ||
| 358 | memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel)); | ||
| 359 | |||
| 360 | // Allocate the tile data. | ||
| 361 | TileData* tile_data = mempool_alloc(&iso->tiles); | ||
| 362 | assert(tile_data); | ||
| 363 | tile_data->width = tile->width; | ||
| 364 | tile_data->height = tile->height; | ||
| 365 | tile_data->pixels_handle = | ||
| 366 | (uint16_t)mem_get_chunk_handle(&iso->pixels, pixels); | ||
| 367 | |||
| 368 | tile = ts_tileset_get_next_tile(tileset, tile); | ||
| 369 | } | ||
| 370 | |||
| 371 | printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd); | ||
| 372 | |||
| 373 | free(tileset); | ||
| 374 | layer = tm_map_get_next_layer(map, layer); | ||
| 375 | } | ||
| 376 | |||
| 377 | // Load the map into the world. | ||
| 378 | layer = &map->layers[0]; | ||
| 379 | // TODO: Handle num_layers layers. | ||
| 380 | for (int i = 0; i < 1; ++i) { | ||
| 381 | memcpy(iso->world, layer->tiles, world_size * sizeof(Tile)); | ||
| 382 | |||
| 383 | // TODO: We need to handle 'firsgid' in TMX files. | ||
| 384 | for (int j = 0; j < world_size; ++j) { | ||
| 385 | iso->world[j] -= 1; | ||
| 386 | } | ||
| 387 | |||
| 388 | layer = tm_map_get_next_layer(map, layer); | ||
| 389 | } | ||
| 390 | |||
| 391 | iso->world_width = map->world_width; | ||
| 392 | iso->world_height = map->world_height; | ||
| 393 | iso->tile_width = map->base_tile_width; | ||
| 394 | iso->tile_height = map->base_tile_height; | ||
| 395 | 260 | ||
| 396 | success = true; | 261 | success = true; |
| 397 | 262 | ||
| 398 | cleanup: | 263 | cleanup: |
| 399 | if (map) { | ||
| 400 | free(map); | ||
| 401 | } | ||
| 402 | if (!success) { | 264 | if (!success) { |
| 403 | destroy_world(iso); | 265 | isogfx_clear(iso); |
| 404 | } | 266 | } |
| 405 | return success; | 267 | return success; |
| 406 | } | 268 | } |
| 407 | 269 | ||
| 408 | int isogfx_world_width(const IsoGfx* iso) { | 270 | int isogfx_world_width(const IsoGfx* iso) { |
| 409 | assert(iso); | 271 | assert(iso); |
| 410 | return iso->world_width; | 272 | return iso->map->world_width; |
| 411 | } | 273 | } |
| 412 | 274 | ||
| 413 | int isogfx_world_height(const IsoGfx* iso) { | 275 | int isogfx_world_height(const IsoGfx* iso) { |
| 414 | assert(iso); | 276 | assert(iso); |
| 415 | return iso->world_height; | 277 | return iso->map->world_height; |
| 416 | } | 278 | } |
| 417 | 279 | ||
| 418 | /// Create a tile mask procedurally. | ||
| 419 | static void make_tile_from_colour( | 280 | static void make_tile_from_colour( |
| 420 | const IsoGfx* iso, Pixel colour, TileData* tile) { | 281 | Pixel colour, const Ts_Tile* tile, Pixel* tile_pixels) { |
| 421 | assert(iso); | ||
| 422 | assert(tile); | 282 | assert(tile); |
| 283 | assert(tile_pixels); | ||
| 423 | 284 | ||
| 424 | const int width = tile->width; | 285 | const int width = tile->width; |
| 425 | const int height = tile->height; | 286 | const int height = tile->height; |
| 426 | const int r = width / height; | 287 | const int r = width / height; |
| 427 | |||
| 428 | for (int y = 0; y < height / 2; ++y) { | 288 | for (int y = 0; y < height / 2; ++y) { |
| 429 | const int mask_start = width / 2 - r * y - 1; | 289 | const int mask_start = width / 2 - r * y - 1; |
| 430 | const int mask_end = width / 2 + r * y + 1; | 290 | const int mask_end = width / 2 + r * y + 1; |
| @@ -433,11 +293,11 @@ static void make_tile_from_colour( | |||
| 433 | const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; | 293 | const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; |
| 434 | 294 | ||
| 435 | // Top half. | 295 | // Top half. |
| 436 | *tile_xy_mut(iso, tile, x, y) = val; | 296 | *ts_tile_xy_mut(tile_pixels, tile, x, y) = val; |
| 437 | 297 | ||
| 438 | // Bottom half reflects the top half. | 298 | // Bottom half reflects the top half. |
| 439 | const int y_reflected = height - y - 1; | 299 | const int y_reflected = height - y - 1; |
| 440 | *tile_xy_mut(iso, tile, x, y_reflected) = val; | 300 | *ts_tile_xy_mut(tile_pixels, tile, x, y_reflected) = val; |
| 441 | } | 301 | } |
| 442 | } | 302 | } |
| 443 | } | 303 | } |
| @@ -445,41 +305,58 @@ static void make_tile_from_colour( | |||
| 445 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { | 305 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { |
| 446 | assert(iso); | 306 | assert(iso); |
| 447 | assert(desc); | 307 | assert(desc); |
| 448 | // Client must create world before creating tiles. | 308 | // Client must create a world first. |
| 449 | assert(iso->tile_width > 0); | 309 | assert(iso->map); |
| 450 | assert(iso->tile_height > 0); | 310 | assert(iso->tileset); |
| 311 | // Currently, procedural tiles must match the base tile size. | ||
| 312 | assert(desc->width == iso->map->base_tile_width); | ||
| 313 | assert(desc->height == iso->map->base_tile_height); | ||
| 314 | // Cannot exceed max tiles. | ||
| 315 | assert(iso->next_tile < iso->tileset->num_tiles); | ||
| 451 | 316 | ||
| 452 | TileData* tile = mempool_alloc(&iso->tiles); | 317 | const Tile tile = iso->next_tile++; |
| 453 | assert(tile); // TODO: Make this a hard assert. | ||
| 454 | 318 | ||
| 455 | const int num_blocks = calc_num_tile_blocks( | 319 | const size_t tile_size_bytes = desc->width * desc->height * sizeof(Pixel); |
| 456 | iso->tile_width, iso->tile_height, desc->width, desc->height); | ||
| 457 | 320 | ||
| 458 | Pixel* pixels = mem_alloc(&iso->pixels, num_blocks); | 321 | switch (desc->type) { |
| 459 | assert(pixels); // TODO: Make this a hard assert. | 322 | case TileFromColour: { |
| 323 | assert(desc->width > 0); | ||
| 324 | assert(desc->height > 0); | ||
| 460 | 325 | ||
| 461 | tile->width = desc->width; | 326 | Ts_Tile* const ts_tile = ts_tileset_get_tile_mut(iso->tileset, tile); |
| 462 | tile->height = desc->height; | ||
| 463 | tile->pixels_handle = mem_get_chunk_handle(&iso->pixels, pixels); | ||
| 464 | 327 | ||
| 465 | switch (desc->type) { | 328 | *ts_tile = (Ts_Tile){ |
| 466 | case TileFromColour: | 329 | .width = iso->map->base_tile_width, |
| 467 | make_tile_from_colour(iso, desc->colour, tile); | 330 | .height = iso->map->base_tile_height, |
| 331 | .pixels = tile * tile_size_bytes, | ||
| 332 | }; | ||
| 333 | |||
| 334 | Pixel* const tile_pixels = | ||
| 335 | ts_tileset_get_tile_pixels_mut(iso->tileset, tile); | ||
| 336 | make_tile_from_colour(desc->colour, ts_tile, tile_pixels); | ||
| 468 | break; | 337 | break; |
| 338 | } | ||
| 469 | case TileFromFile: | 339 | case TileFromFile: |
| 470 | assert(false); // TODO | 340 | assert(false); // TODO |
| 471 | break; | 341 | break; |
| 472 | case TileFromMemory: | 342 | case TileFromMemory: { |
| 343 | assert(desc->width > 0); | ||
| 344 | assert(desc->height > 0); | ||
| 473 | assert(false); // TODO | 345 | assert(false); // TODO |
| 474 | break; | 346 | break; |
| 475 | } | 347 | } |
| 348 | } | ||
| 476 | 349 | ||
| 477 | return (Tile)mempool_get_block_index(&iso->tiles, tile); | 350 | return tile; |
| 478 | } | 351 | } |
| 479 | 352 | ||
| 480 | void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { | 353 | void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { |
| 481 | assert(iso); | 354 | assert(iso); |
| 482 | *world_xy_mut(iso, x, y) = tile; | 355 | |
| 356 | Tm_Layer* const layer = tm_map_get_layer_mut(iso->map, 0); | ||
| 357 | Tile* map_tile = tm_layer_get_tile_mut(iso->map, layer, x, y); | ||
| 358 | |||
| 359 | *map_tile = tile; | ||
| 483 | } | 360 | } |
| 484 | 361 | ||
| 485 | void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { | 362 | void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { |
| @@ -491,88 +368,67 @@ void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { | |||
| 491 | } | 368 | } |
| 492 | } | 369 | } |
| 493 | 370 | ||
| 494 | bool isogfx_load_sprite_sheet( | 371 | SpriteSheet isogfx_load_sprite_sheet(IsoGfx* iso, const char* filepath) { |
| 495 | IsoGfx* iso, const char* filepath, SpriteSheet* p_sheet) { | ||
| 496 | assert(iso); | 372 | assert(iso); |
| 497 | assert(filepath); | 373 | assert(filepath); |
| 498 | assert(p_sheet); | ||
| 499 | 374 | ||
| 500 | bool success = false; | 375 | bool success = false; |
| 501 | 376 | SpriteSheet spriteSheet = 0; | |
| 502 | // Lazy initialization of sprite pools. | 377 | const size_t watermark = memstack_get_watermark(&iso->stack); |
| 503 | if (mempool_capacity(&iso->sprites) == 0) { | ||
| 504 | if (!mempool_make_dyn( | ||
| 505 | &iso->sprites, iso->max_num_sprites, sizeof(SpriteData))) { | ||
| 506 | return false; | ||
| 507 | } | ||
| 508 | } | ||
| 509 | if (mem_capacity(&iso->sheets) == 0) { | ||
| 510 | // Using a block size of 1 byte for sprite sheet data. | ||
| 511 | if (!mem_make_dyn(&iso->sheets, iso->sprite_sheet_pool_size_bytes, 1)) { | ||
| 512 | return false; | ||
| 513 | } | ||
| 514 | } | ||
| 515 | 378 | ||
| 516 | // Load sprite sheet file. | 379 | // Load sprite sheet file. |
| 517 | printf("Load sprite sheet: %s\n", filepath); | 380 | printf("Load sprite sheet: %s\n", filepath); |
| 518 | FILE* file = fopen(filepath, "rb"); | 381 | Ss_SpriteSheet* ss_sheet = nullptr; |
| 519 | if (file == NULL) { | 382 | WITH_FILE(filepath, { |
| 520 | goto cleanup; | 383 | const size_t file_size = get_file_size_f(file); |
| 521 | } | 384 | ss_sheet = |
| 522 | const size_t sheet_size = get_file_size(file); | 385 | memstack_alloc_aligned(&iso->stack, file_size, alignof(Ss_SpriteSheet)); |
| 523 | Ss_SpriteSheet* ss_sheet = mem_alloc(&iso->sheets, sheet_size); | 386 | success = read_file_f(file, ss_sheet); |
| 524 | if (!ss_sheet) { | 387 | }); |
| 525 | goto cleanup; | 388 | if (!success) { |
| 526 | } | ||
| 527 | if (fread(ss_sheet, sheet_size, 1, file) != 1) { | ||
| 528 | goto cleanup; | 389 | goto cleanup; |
| 529 | } | 390 | } |
| 391 | assert(ss_sheet); | ||
| 530 | 392 | ||
| 531 | *p_sheet = mem_get_chunk_handle(&iso->sheets, ss_sheet); | 393 | spriteSheet = (SpriteSheet)ss_sheet; |
| 532 | success = true; | ||
| 533 | 394 | ||
| 534 | cleanup: | 395 | cleanup: |
| 535 | // Pools remain initialized since client may attempt to load other sprites. | ||
| 536 | if (file != NULL) { | ||
| 537 | fclose(file); | ||
| 538 | } | ||
| 539 | if (!success) { | 396 | if (!success) { |
| 540 | if (ss_sheet) { | 397 | if (ss_sheet) { |
| 541 | mem_free(&iso->sheets, &ss_sheet); | 398 | memstack_set_watermark(&iso->stack, watermark); |
| 542 | } | 399 | } |
| 543 | } | 400 | } |
| 544 | return success; | 401 | return spriteSheet; |
| 545 | } | 402 | } |
| 546 | 403 | ||
| 547 | Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) { | 404 | Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) { |
| 548 | assert(iso); | 405 | assert(iso); |
| 406 | assert(sheet); | ||
| 549 | 407 | ||
| 550 | SpriteData* sprite = mempool_alloc(&iso->sprites); | 408 | // TODO: Remove memstack_alloc() and replace it with a same-name macro that |
| 551 | assert(sprite); | 409 | // calls memstack_alloc_aligned() with sizeof/alignof. No real point in |
| 410 | // having unaligned allocations. | ||
| 411 | SpriteInstance* sprite = memstack_alloc_aligned( | ||
| 412 | &iso->stack, sizeof(SpriteInstance), alignof(SpriteInstance)); | ||
| 552 | 413 | ||
| 553 | sprite->sheet = sheet; | 414 | sprite->sheet = (const Ss_SpriteSheet*)sheet; |
| 415 | sprite->next = iso->head_sprite; | ||
| 416 | iso->head_sprite = sprite; | ||
| 554 | 417 | ||
| 555 | return mempool_get_block_index(&iso->sprites, sprite); | 418 | return (Sprite)sprite; |
| 556 | } | 419 | } |
| 557 | 420 | ||
| 558 | #define with_sprite(SPRITE, BODY) \ | 421 | void isogfx_set_sprite_position(IsoGfx* iso, Sprite hSprite, int x, int y) { |
| 559 | { \ | ||
| 560 | SpriteData* data = mempool_get_block(&iso->sprites, sprite); \ | ||
| 561 | assert(data); \ | ||
| 562 | BODY; \ | ||
| 563 | } | ||
| 564 | |||
| 565 | void isogfx_set_sprite_position(IsoGfx* iso, Sprite sprite, int x, int y) { | ||
| 566 | assert(iso); | 422 | assert(iso); |
| 567 | with_sprite(sprite, { | 423 | SpriteInstance* sprite = (SpriteInstance*)hSprite; |
| 568 | data->position.x = x; | 424 | sprite->position.x = x; |
| 569 | data->position.y = y; | 425 | sprite->position.y = y; |
| 570 | }); | ||
| 571 | } | 426 | } |
| 572 | 427 | ||
| 573 | void isogfx_set_sprite_animation(IsoGfx* iso, Sprite sprite, int animation) { | 428 | void isogfx_set_sprite_animation(IsoGfx* iso, Sprite hSprite, int animation) { |
| 574 | assert(iso); | 429 | assert(iso); |
| 575 | with_sprite(sprite, { data->animation = animation; }); | 430 | SpriteInstance* sprite = (SpriteInstance*)hSprite; |
| 431 | sprite->animation = animation; | ||
| 576 | } | 432 | } |
| 577 | 433 | ||
| 578 | void isogfx_update(IsoGfx* iso, double t) { | 434 | void isogfx_update(IsoGfx* iso, double t) { |
| @@ -586,14 +442,14 @@ void isogfx_update(IsoGfx* iso, double t) { | |||
| 586 | } | 442 | } |
| 587 | 443 | ||
| 588 | if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) { | 444 | if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) { |
| 589 | // TODO: Consider linking animated sprites in a list so that we only walk | 445 | // TODO: Consider linking animated sprites in a separate list so that we |
| 590 | // over those here and not also the static sprites. | 446 | // only walk over those here and not also the static sprites. |
| 591 | mempool_foreach(&iso->sprites, sprite, { | 447 | for (SpriteInstance* sprite = iso->head_sprite; sprite; |
| 592 | const Ss_SpriteSheet* sheet = mem_get_chunk(&iso->sheets, sprite->sheet); | 448 | sprite = sprite->next) { |
| 593 | assert(sheet); // TODO: Make this a hard assert inside the mem/pool. | 449 | const Ss_SpriteSheet* sheet = sprite->sheet; |
| 594 | const Ss_Row* row = get_sprite_sheet_row(sheet, sprite->animation); | 450 | const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); |
| 595 | sprite->frame = (sprite->frame + 1) % row->num_cols; | 451 | sprite->frame = (sprite->frame + 1) % row->num_cols; |
| 596 | }); | 452 | } |
| 597 | 453 | ||
| 598 | iso->last_animation_time = t; | 454 | iso->last_animation_time = t; |
| 599 | } | 455 | } |
| @@ -614,8 +470,10 @@ typedef struct CoordSystem { | |||
| 614 | static CoordSystem make_iso_coord_system(const IsoGfx* iso) { | 470 | static CoordSystem make_iso_coord_system(const IsoGfx* iso) { |
| 615 | assert(iso); | 471 | assert(iso); |
| 616 | const ivec2 o = {iso->screen_width / 2, 0}; | 472 | const ivec2 o = {iso->screen_width / 2, 0}; |
| 617 | const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; | 473 | const ivec2 x = { |
| 618 | const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; | 474 | .x = iso->map->base_tile_width / 2, .y = iso->map->base_tile_height / 2}; |
| 475 | const ivec2 y = { | ||
| 476 | .x = -iso->map->base_tile_width / 2, .y = iso->map->base_tile_height / 2}; | ||
| 619 | return (CoordSystem){o, x, y}; | 477 | return (CoordSystem){o, x, y}; |
| 620 | } | 478 | } |
| 621 | 479 | ||
| @@ -696,19 +554,20 @@ static void draw_rect( | |||
| 696 | /// World (0, 0) -> (screen_width / 2, 0). | 554 | /// World (0, 0) -> (screen_width / 2, 0). |
| 697 | static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) { | 555 | static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) { |
| 698 | assert(iso); | 556 | assert(iso); |
| 557 | assert(iso->tileset); | ||
| 699 | 558 | ||
| 700 | const TileData* tile_data = mempool_get_block(&iso->tiles, tile); | 559 | const Ts_Tile* pTile = ts_tileset_get_tile(iso->tileset, tile); |
| 701 | assert(tile_data); | 560 | const Pixel* pixels = ts_tileset_get_tile_pixels(iso->tileset, tile); |
| 702 | const Pixel* pixels = tile_xy_const_ref(iso, tile_data, 0, 0); | ||
| 703 | 561 | ||
| 704 | // Move from the top diamond-corner to the top-left corner of the tile image. | 562 | // Move from the top diamond-corner to the top-left corner of the tile image. |
| 705 | // For regular tiles, tile height == base tile height, so the y offset is 0. | 563 | // For regular tiles, tile height == base tile height, so the y offset is 0. |
| 706 | // For super tiles, move as high up as the height of the tile. | 564 | // For super tiles, move as high up as the height of the tile. |
| 707 | const ivec2 offset = { | 565 | const ivec2 offset = { |
| 708 | -(iso->tile_width / 2), tile_data->height - iso->tile_height}; | 566 | -(iso->map->base_tile_width / 2), |
| 567 | pTile->height - iso->map->base_tile_height}; | ||
| 709 | const ivec2 top_left = ivec2_add(screen_origin, offset); | 568 | const ivec2 top_left = ivec2_add(screen_origin, offset); |
| 710 | 569 | ||
| 711 | draw_rect(iso, top_left, tile_data->width, tile_data->height, pixels, 0); | 570 | draw_rect(iso, top_left, pTile->width, pTile->height, pixels, 0); |
| 712 | } | 571 | } |
| 713 | 572 | ||
| 714 | static void draw_world(IsoGfx* iso) { | 573 | static void draw_world(IsoGfx* iso) { |
| @@ -721,14 +580,16 @@ static void draw_world(IsoGfx* iso) { | |||
| 721 | 580 | ||
| 722 | const CoordSystem iso_space = make_iso_coord_system(iso); | 581 | const CoordSystem iso_space = make_iso_coord_system(iso); |
| 723 | 582 | ||
| 583 | const Tm_Layer* layer = tm_map_get_layer(iso->map, 0); | ||
| 584 | |||
| 724 | // TODO: Culling. | 585 | // TODO: Culling. |
| 725 | // Ex: map the screen corners to tile space to cull. | 586 | // Ex: map the screen corners to tile space to cull. |
| 726 | // Ex: walk in screen space and fetch the tile. | 587 | // Ex: walk in screen space and fetch the tile. |
| 727 | // The tile-centric approach might be more cache-friendly since the | 588 | // The tile-centric approach might be more cache-friendly since the |
| 728 | // screen-centric approach would juggle multiple tiles throughout the scan. | 589 | // screen-centric approach would juggle multiple tiles throughout the scan. |
| 729 | for (int wy = 0; wy < iso->world_height; ++wy) { | 590 | for (int wy = 0; wy < iso->map->world_height; ++wy) { |
| 730 | for (int wx = 0; wx < iso->world_width; ++wx) { | 591 | for (int wx = 0; wx < iso->map->world_width; ++wx) { |
| 731 | const Tile tile = world_xy(iso, wx, wy); | 592 | const Tile tile = tm_layer_get_tile(iso->map, layer, wx, wy); |
| 732 | const ivec2 screen_origin = GetTileScreenOrigin(iso_space, wx, wy); | 593 | const ivec2 screen_origin = GetTileScreenOrigin(iso_space, wx, wy); |
| 733 | draw_tile(iso, screen_origin, tile); | 594 | draw_tile(iso, screen_origin, tile); |
| 734 | } | 595 | } |
| @@ -736,7 +597,7 @@ static void draw_world(IsoGfx* iso) { | |||
| 736 | } | 597 | } |
| 737 | 598 | ||
| 738 | static void draw_sprite( | 599 | static void draw_sprite( |
| 739 | IsoGfx* iso, ivec2 origin, const SpriteData* sprite, | 600 | IsoGfx* iso, ivec2 origin, const SpriteInstance* sprite, |
| 740 | const Ss_SpriteSheet* sheet) { | 601 | const Ss_SpriteSheet* sheet) { |
| 741 | assert(iso); | 602 | assert(iso); |
| 742 | assert(sprite); | 603 | assert(sprite); |
| @@ -745,8 +606,8 @@ static void draw_sprite( | |||
| 745 | assert(sprite->animation < sheet->num_rows); | 606 | assert(sprite->animation < sheet->num_rows); |
| 746 | assert(sprite->frame >= 0); | 607 | assert(sprite->frame >= 0); |
| 747 | 608 | ||
| 748 | const Ss_Row* row = get_sprite_sheet_row(sheet, sprite->animation); | 609 | const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); |
| 749 | const uint8_t* frame = get_sprite_sheet_sprite(sheet, row, sprite->frame); | 610 | const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame); |
| 750 | draw_rect( | 611 | draw_rect( |
| 751 | iso, origin, sheet->sprite_width, sheet->sprite_height, | 612 | iso, origin, sheet->sprite_width, sheet->sprite_height, |
| 752 | sheet->palette.colours, frame); | 613 | sheet->palette.colours, frame); |
| @@ -757,14 +618,15 @@ static void draw_sprites(IsoGfx* iso) { | |||
| 757 | 618 | ||
| 758 | const CoordSystem iso_space = make_iso_coord_system(iso); | 619 | const CoordSystem iso_space = make_iso_coord_system(iso); |
| 759 | 620 | ||
| 760 | mempool_foreach(&iso->sprites, sprite, { | 621 | for (const SpriteInstance* sprite = iso->head_sprite; sprite; |
| 761 | const Ss_SpriteSheet* sheet = mem_get_chunk(&iso->sheets, sprite->sheet); | 622 | sprite = sprite->next) { |
| 623 | const Ss_SpriteSheet* sheet = sprite->sheet; | ||
| 762 | assert(sheet); | 624 | assert(sheet); |
| 763 | 625 | ||
| 764 | const ivec2 screen_origin = | 626 | const ivec2 screen_origin = |
| 765 | GetTileScreenOrigin(iso_space, sprite->position.x, sprite->position.y); | 627 | GetTileScreenOrigin(iso_space, sprite->position.x, sprite->position.y); |
| 766 | draw_sprite(iso, screen_origin, sprite, sheet); | 628 | draw_sprite(iso, screen_origin, sprite, sheet); |
| 767 | }); | 629 | } |
| 768 | } | 630 | } |
| 769 | 631 | ||
| 770 | void isogfx_render(IsoGfx* iso) { | 632 | void isogfx_render(IsoGfx* iso) { |
| @@ -777,35 +639,14 @@ void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) { | |||
| 777 | assert(iso); | 639 | assert(iso); |
| 778 | assert(x >= 0); | 640 | assert(x >= 0); |
| 779 | assert(y >= 0); | 641 | assert(y >= 0); |
| 780 | assert(x < iso->world_width); | 642 | assert(x < iso->map->world_width); |
| 781 | assert(y < iso->world_height); | 643 | assert(y < iso->map->world_height); |
| 782 | 644 | ||
| 783 | const CoordSystem iso_space = make_iso_coord_system(iso); | 645 | const CoordSystem iso_space = make_iso_coord_system(iso); |
| 784 | const ivec2 screen_origin = GetTileScreenOrigin(iso_space, x, y); | 646 | const ivec2 screen_origin = GetTileScreenOrigin(iso_space, x, y); |
| 785 | draw_tile(iso, screen_origin, tile); | 647 | draw_tile(iso, screen_origin, tile); |
| 786 | } | 648 | } |
| 787 | 649 | ||
| 788 | bool isogfx_resize(IsoGfx* iso, int screen_width, int screen_height) { | ||
| 789 | assert(iso); | ||
| 790 | assert(iso->screen); | ||
| 791 | |||
| 792 | const int current_size = iso->screen_width * iso->screen_height; | ||
| 793 | const int new_size = screen_width * screen_height; | ||
| 794 | |||
| 795 | if (new_size > current_size) { | ||
| 796 | Pixel* new_screen = calloc(new_size, sizeof(Pixel)); | ||
| 797 | if (new_screen) { | ||
| 798 | free(iso->screen); | ||
| 799 | iso->screen = new_screen; | ||
| 800 | } else { | ||
| 801 | return false; | ||
| 802 | } | ||
| 803 | } | ||
| 804 | iso->screen_width = screen_width; | ||
| 805 | iso->screen_height = screen_height; | ||
| 806 | return true; | ||
| 807 | } | ||
| 808 | |||
| 809 | void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) { | 650 | void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) { |
| 810 | assert(iso); | 651 | assert(iso); |
| 811 | assert(width); | 652 | assert(width); |
| @@ -826,11 +667,11 @@ void isogfx_pick_tile( | |||
| 826 | assert(yiso); | 667 | assert(yiso); |
| 827 | 668 | ||
| 828 | const vec2 xy_iso = cart2iso( | 669 | const vec2 xy_iso = cart2iso( |
| 829 | (vec2){.x = xcart, .y = ycart}, iso->tile_width, iso->tile_height, | 670 | (vec2){.x = xcart, .y = ycart}, iso->map->base_tile_width, |
| 830 | iso->screen_width); | 671 | iso->map->base_tile_height, iso->screen_width); |
| 831 | 672 | ||
| 832 | if ((0 <= xy_iso.x) && (xy_iso.x < iso->world_width) && (0 <= xy_iso.y) && | 673 | if ((0 <= xy_iso.x) && (xy_iso.x < iso->map->world_width) && |
| 833 | (xy_iso.y < iso->world_height)) { | 674 | (0 <= xy_iso.y) && (xy_iso.y < iso->map->world_height)) { |
| 834 | *xiso = (int)xy_iso.x; | 675 | *xiso = (int)xy_iso.x; |
| 835 | *yiso = (int)xy_iso.y; | 676 | *yiso = (int)xy_iso.y; |
| 836 | } else { | 677 | } else { |
diff --git a/tools/mkasset.py b/tools/mkasset.py index fd2ead7..ae27ead 100644 --- a/tools/mkasset.py +++ b/tools/mkasset.py | |||
| @@ -51,10 +51,15 @@ def convert_tsx(input_filepath, output_filepath): | |||
| 51 | print(f"Max width: {max_tile_width}") | 51 | print(f"Max width: {max_tile_width}") |
| 52 | print(f"Max height: {max_tile_height}") | 52 | print(f"Max height: {max_tile_height}") |
| 53 | 53 | ||
| 54 | pixels = [] # List of byte arrays | ||
| 55 | pixels_offset = 0 | ||
| 56 | |||
| 54 | with open(output_filepath, 'bw') as output: | 57 | with open(output_filepath, 'bw') as output: |
| 58 | # Write the header. | ||
| 55 | output.write(ctypes.c_uint16(tile_count)) | 59 | output.write(ctypes.c_uint16(tile_count)) |
| 56 | output.write(ctypes.c_uint16(max_tile_width)) | 60 | # output.write(ctypes.c_uint16(max_tile_width)) |
| 57 | output.write(ctypes.c_uint16(max_tile_height)) | 61 | # output.write(ctypes.c_uint16(max_tile_height)) |
| 62 | output.write(ctypes.c_uint16(0)) # Pad. | ||
| 58 | 63 | ||
| 59 | num_tile = 0 | 64 | num_tile = 0 |
| 60 | for tile in root: | 65 | for tile in root: |
| @@ -72,12 +77,23 @@ def convert_tsx(input_filepath, output_filepath): | |||
| 72 | tile_height = int(image.attrib["height"]) | 77 | tile_height = int(image.attrib["height"]) |
| 73 | tile_path = image.attrib["source"] | 78 | tile_path = image.attrib["source"] |
| 74 | 79 | ||
| 75 | output.write(ctypes.c_uint16(tile_width)) | 80 | assert (tile_width > 0) |
| 76 | output.write(ctypes.c_uint16(tile_height)) | 81 | assert (tile_height > 0) |
| 77 | 82 | ||
| 83 | tile_pixels_offset = pixels_offset | ||
| 78 | with Image.open(tile_path) as im: | 84 | with Image.open(tile_path) as im: |
| 79 | bytes = im.convert('RGBA').tobytes() | 85 | bytes = im.convert('RGBA').tobytes() |
| 80 | output.write(bytes) | 86 | pixels.append(bytes) |
| 87 | pixels_offset += len(bytes) | ||
| 88 | |||
| 89 | # Write the tile. | ||
| 90 | output.write(ctypes.c_uint16(tile_width)) | ||
| 91 | output.write(ctypes.c_uint16(tile_height)) | ||
| 92 | output.write(ctypes.c_uint32(tile_pixels_offset)) | ||
| 93 | |||
| 94 | # Write the pixel data. | ||
| 95 | for bytes in pixels: | ||
| 96 | output.write(bytes) | ||
| 81 | 97 | ||
| 82 | 98 | ||
| 83 | def convert_tmx(input_filepath, output_filepath): | 99 | def convert_tmx(input_filepath, output_filepath): |
| @@ -96,23 +112,27 @@ def convert_tmx(input_filepath, output_filepath): | |||
| 96 | print(f"Tile width: {base_tile_width}") | 112 | print(f"Tile width: {base_tile_width}") |
| 97 | print(f"Tile height: {base_tile_height}") | 113 | print(f"Tile height: {base_tile_height}") |
| 98 | 114 | ||
| 99 | with open(output_filepath, 'bw') as output: | 115 | tileset_path = None |
| 100 | output.write(ctypes.c_uint16(map_width)) | ||
| 101 | output.write(ctypes.c_uint16(map_height)) | ||
| 102 | output.write(ctypes.c_uint16(base_tile_width)) | ||
| 103 | output.write(ctypes.c_uint16(base_tile_height)) | ||
| 104 | output.write(ctypes.c_uint16(num_layers)) | ||
| 105 | |||
| 106 | tileset_path = None | ||
| 107 | 116 | ||
| 117 | with open(output_filepath, 'bw') as output: | ||
| 108 | for child in root: | 118 | for child in root: |
| 109 | if child.tag == "tileset": | 119 | if child.tag == "tileset": |
| 120 | assert (not tileset_path) # Only supporting one tile set per map right now. | ||
| 121 | |||
| 110 | tileset = child | 122 | tileset = child |
| 111 | tileset_path = tileset.attrib["source"] | 123 | tileset_path = tileset.attrib["source"] |
| 112 | 124 | ||
| 113 | print(f"Tile set: {tileset_path}") | 125 | print(f"Tile set: {tileset_path}") |
| 114 | 126 | ||
| 115 | tileset_path = tileset_path.replace("tsx", "ts") | 127 | tileset_path = tileset_path.replace("tsx", "ts") |
| 128 | |||
| 129 | # Write the header. | ||
| 130 | output.write(to_char_array(tileset_path, MAX_PATH_LENGTH)) | ||
| 131 | output.write(ctypes.c_uint16(map_width)) | ||
| 132 | output.write(ctypes.c_uint16(map_height)) | ||
| 133 | output.write(ctypes.c_uint16(base_tile_width)) | ||
| 134 | output.write(ctypes.c_uint16(base_tile_height)) | ||
| 135 | output.write(ctypes.c_uint16(num_layers)) | ||
| 116 | elif child.tag == "layer": | 136 | elif child.tag == "layer": |
| 117 | layer = child | 137 | layer = child |
| 118 | layer_id = int(layer.attrib["id"]) | 138 | layer_id = int(layer.attrib["id"]) |
| @@ -123,9 +143,6 @@ def convert_tmx(input_filepath, output_filepath): | |||
| 123 | print(f"Width: {layer_width}") | 143 | print(f"Width: {layer_width}") |
| 124 | print(f"Height: {layer_height}") | 144 | print(f"Height: {layer_height}") |
| 125 | 145 | ||
| 126 | assert (tileset_path) | ||
| 127 | output.write(to_char_array(tileset_path, MAX_PATH_LENGTH)) | ||
| 128 | |||
| 129 | # Assume the layer's dimensions matches the map's. | 146 | # Assume the layer's dimensions matches the map's. |
| 130 | assert (layer_width == map_width) | 147 | assert (layer_width == map_width) |
| 131 | assert (layer_height == map_height) | 148 | assert (layer_height == map_height) |
| @@ -139,7 +156,9 @@ def convert_tmx(input_filepath, output_filepath): | |||
| 139 | for row in rows: | 156 | for row in rows: |
| 140 | tile_ids = [x.strip() for x in row.split(',') if x] | 157 | tile_ids = [x.strip() for x in row.split(',') if x] |
| 141 | for tile_id in tile_ids: | 158 | for tile_id in tile_ids: |
| 142 | output.write(ctypes.c_uint16(int(tile_id))) | 159 | # TODO: We need to handle 'firsgid' in TMX files. |
| 160 | # For now, assume it's 1 and do -1 to make the tiles 0-based. | ||
| 161 | output.write(ctypes.c_uint16(int(tile_id) - 1)) | ||
| 143 | 162 | ||
| 144 | 163 | ||
| 145 | def get_num_cols(image, sprite_width): | 164 | def get_num_cols(image, sprite_width): |
