summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-07-19 09:29:12 -0700
committer3gg <3gg@shellblade.net>2025-07-19 09:29:12 -0700
commitd1370b955f9a86c82f92d7368237ed96318de330 (patch)
treefe59a07927e560da5dfe88c89c547ec3c3a47307
parent10cd24c9e5da615064c782effafc7477bf074054 (diff)
Allocate data from a stack allocator
-rw-r--r--CMakeLists.txt4
-rw-r--r--demos/checkerboard/checkerboard.c29
-rw-r--r--demos/isomap/isomap.c24
-rw-r--r--include/isogfx/asset.h149
-rw-r--r--include/isogfx/isogfx.h57
-rw-r--r--include/isogfx/types.h14
-rw-r--r--src/asset.c34
-rw-r--r--src/isogfx.c605
-rw-r--r--tools/mkasset.py53
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
11add_library(isogfx 11add_library(isogfx
12 src/asset.c
12 src/isogfx.c) 13 src/isogfx.c)
13 14
14target_include_directories(isogfx PUBLIC 15target_include_directories(isogfx PUBLIC
@@ -16,8 +17,7 @@ target_include_directories(isogfx PUBLIC
16 17
17target_link_libraries(isogfx PUBLIC 18target_link_libraries(isogfx PUBLIC
18 filesystem 19 filesystem
19 mem 20 memstack)
20 mempool)
21 21
22target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) 22target_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;
20static const int WORLD_WIDTH = 20; 20static const int WORLD_WIDTH = 20;
21static const int WORLD_HEIGHT = 20; 21static const int WORLD_HEIGHT = 20;
22 22
23#define MEMORY_SIZE (2 * 1024 * 1024)
24uint8_t MEMORY[MEMORY_SIZE];
25
23static const TileDesc tile_set[] = { 26static 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
38typedef enum Colour { 43typedef 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
9static const int WINDOW_WIDTH = 1408; 10static const int WINDOW_WIDTH = 1408;
10static const int WINDOW_HEIGHT = 960; 11static const int WINDOW_HEIGHT = 960;
@@ -14,6 +15,9 @@ static const int MAX_FPS = 60;
14static const int SCREEN_WIDTH = 704; 15static const int SCREEN_WIDTH = 704;
15static const int SCREEN_HEIGHT = 480; 16static const int SCREEN_HEIGHT = 480;
16 17
18#define MEMORY_SIZE (2 * 1024 * 1024)
19uint8_t MEMORY[MEMORY_SIZE];
20
17typedef struct GfxAppState { 21typedef 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
20typedef struct Ts_Tile { 22typedef 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
26typedef struct Ts_TileSet { 28typedef 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
37typedef struct Tm_Layer { 39typedef 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
44typedef struct Tm_Map { 43typedef 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.
92static inline const Ts_Tile* ts_tileset_get_next_tile( 92static 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.
99static 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.
108static 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.
114static 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.
125static 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.
131static 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).
139static 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).
151static 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.
101static inline const Tm_Layer* tm_map_get_next_layer( 157static 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.
168static 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.
176static 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.
188static 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.
194static 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.
111static inline const Ss_Row* get_sprite_sheet_row( 200static 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.
123static inline const uint8_t* get_sprite_sheet_sprite( 212static 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.
228bool ts_validate_tileset(const Ts_TileSet* tileset);
229
230/// Validate the map.
231bool 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
9typedef struct IsoGfx IsoGfx; 11typedef struct IsoGfx IsoGfx;
10 12
11/// Sprite sheet handle. 13/// Sprite sheet handle.
12typedef uint16_t SpriteSheet; 14typedef uintptr_t SpriteSheet;
13 15
14/// Sprite handle. 16/// Sprite handle.
15typedef uint16_t Sprite; 17typedef uintptr_t Sprite;
16
17/// Tile handle.
18typedef uint16_t Tile;
19
20/// Colour channel.
21typedef uint8_t Channel;
22
23typedef struct Pixel {
24 Channel r, g, b, a;
25} Pixel;
26 18
27typedef enum TileDescType { 19typedef enum TileDescType {
28 TileFromColour, 20 TileFromColour,
@@ -32,32 +24,32 @@ typedef enum TileDescType {
32 24
33typedef struct TileDesc { 25typedef 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
48typedef struct WorldDesc { 40typedef 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
56typedef struct IsoGfxDesc { 48typedef 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.
67void isogfx_del(IsoGfx**); 59void isogfx_del(IsoGfx**);
68 60
61/// Clear all loaded worlds and sprites.
62void isogfx_clear(IsoGfx*);
63
69/// Create an empty world. 64/// Create an empty world.
70bool isogfx_make_world(IsoGfx*, const WorldDesc*); 65void 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.
73bool isogfx_load_world(IsoGfx*, const char* filepath); 68bool isogfx_load_world(IsoGfx*, const char* filepath);
@@ -88,14 +83,11 @@ void isogfx_set_tile(IsoGfx*, int x, int y, Tile);
88void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); 83void 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.
91bool isogfx_load_sprite_sheet(IsoGfx*, const char* filepath, SpriteSheet*); 86SpriteSheet isogfx_load_sprite_sheet(IsoGfx*, const char* filepath);
92 87
93/// Create an animated sprite. 88/// Create an animated sprite.
94Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet); 89Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet);
95 90
96/// Destroy the sprite.
97void isogfx_del_sprite(IsoGfx*, Sprite);
98
99/// Destroy all the sprites. 91/// Destroy all the sprites.
100void isogfx_del_sprites(IsoGfx*); 92void 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().
121void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); 113void isogfx_draw_tile(IsoGfx*, int x, int y, Tile);
122 114
123/// Resize the virtual screen's dimensions.
124bool isogfx_resize(IsoGfx*, int screen_width, int screen_height);
125
126/// Get the virtual screen's dimensions. 115/// Get the virtual screen's dimensions.
127void isogfx_get_screen_size(const IsoGfx*, int* width, int* height); 116void 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.
6typedef uint8_t Channel;
7
8/// Pixel.
9typedef struct Pixel {
10 Channel r, g, b, a;
11} Pixel;
12
13/// Tile handle/index.
14typedef 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
3bool 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
19bool 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
46typedef 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
52typedef 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
59DEF_MEMPOOL_DYN(TilePool, TileData) 37typedef struct SpriteInstance {
60DEF_MEM_DYN(PixelPool, Pixel) 38 struct SpriteInstance* next;
61 39 const Ss_SpriteSheet* sheet;
62DEF_MEMPOOL_DYN(SpritePool, SpriteData) 40 ivec2 position;
63DEF_MEM_DYN(SpriteSheetPool, Ss_SpriteSheet) 41 int animation; // Current animation.
42 int frame; // Current frame of animation.
43} SpriteInstance;
64 44
65typedef struct IsoGfx { 45typedef 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
117static 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
132static 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
136static 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
145static inline Tile world_xy(const IsoGfx* iso, int x, int y) {
146 return *world_xy_const_ref(iso, x, y);
147}
148
149static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
150 return (Tile*)world_xy_const_ref(iso, x, y);
151}
152
153static inline const Pixel* screen_xy_const_ref( 92static 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
171static 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
214cleanup: 142cleanup:
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. 147void isogfx_clear(IsoGfx* iso) {
220static 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}
231static void destroy_sprites(IsoGfx* iso) { 158
232 assert(iso); 159void isogfx_del(IsoGfx** ppIso) {
233 mempool_del(&iso->sprites); 160 assert(ppIso);
234 mem_del(&iso->sheets); 161 IsoGfx* iso = *ppIso;
235}
236
237void 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
252bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { 168void 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,
288cleanup: 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
293bool isogfx_load_world(IsoGfx* iso, const char* filepath) { 214bool 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
398cleanup: 263cleanup:
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
408int isogfx_world_width(const IsoGfx* iso) { 270int 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
413int isogfx_world_height(const IsoGfx* iso) { 275int 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.
419static void make_tile_from_colour( 280static 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(
445Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { 305Tile 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
480void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { 353void 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
485void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { 362void 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
494bool isogfx_load_sprite_sheet( 371SpriteSheet 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
534cleanup: 395cleanup:
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
547Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) { 404Sprite 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) \ 421void 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
565void 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
573void isogfx_set_sprite_animation(IsoGfx* iso, Sprite sprite, int animation) { 428void 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
578void isogfx_update(IsoGfx* iso, double t) { 434void 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 {
614static CoordSystem make_iso_coord_system(const IsoGfx* iso) { 470static 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).
697static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) { 555static 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
714static void draw_world(IsoGfx* iso) { 573static 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
738static void draw_sprite( 599static 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
770void isogfx_render(IsoGfx* iso) { 632void 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
788bool 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
809void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) { 650void 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
83def convert_tmx(input_filepath, output_filepath): 99def 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
145def get_num_cols(image, sprite_width): 164def get_num_cols(image, sprite_width):