summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asset.c34
-rw-r--r--src/isogfx.c605
2 files changed, 257 insertions, 382 deletions
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 {