summaryrefslogtreecommitdiff
path: root/gfx-iso/src/isogfx.c
diff options
context:
space:
mode:
Diffstat (limited to 'gfx-iso/src/isogfx.c')
-rw-r--r--gfx-iso/src/isogfx.c362
1 files changed, 321 insertions, 41 deletions
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c
index 3ed0fde..9ba1bec 100644
--- a/gfx-iso/src/isogfx.c
+++ b/gfx-iso/src/isogfx.c
@@ -13,9 +13,29 @@
13#include <stdlib.h> 13#include <stdlib.h>
14#include <string.h> 14#include <string.h>
15 15
16/// Maximum number of tiles unless the user chooses a non-zero value. 16/// Maximum number of tiles unless the user specifies a value.
17#define DEFAULT_MAX_NUM_TILES 1024 17#define DEFAULT_MAX_NUM_TILES 1024
18 18
19/// Maximum number of sprites unless the user specifies a value.
20#define DEFAULT_MAX_NUM_SPRITES 128
21
22/// Size of sprite sheet pool in bytes unless the user specifies a value.
23#define DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES (8 * 1024 * 1024)
24
25/// Default animation speed.
26#define ANIMATION_FPS 10
27
28/// Time between animation updates.
29#define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS)
30
31typedef struct ivec2 {
32 int x, y;
33} ivec2;
34
35typedef struct vec2 {
36 double x, y;
37} vec2;
38
19// ----------------------------------------------------------------------------- 39// -----------------------------------------------------------------------------
20// Tile set (TS) and tile map (TM) file formats. 40// Tile set (TS) and tile map (TM) file formats.
21// ----------------------------------------------------------------------------- 41// -----------------------------------------------------------------------------
@@ -70,6 +90,39 @@ static inline const Ts_Tile* ts_tileset_get_next_tile(
70} 90}
71 91
72// ----------------------------------------------------------------------------- 92// -----------------------------------------------------------------------------
93// Sprite sheet file format.
94// -----------------------------------------------------------------------------
95
96/// A row of sprites in a sprite sheet.
97///
98/// Each row in a sprite sheet can have a different number of columns.
99///
100/// The pixels of the row follow a "sprite-major" order. It contains the
101/// 'sprite_width * sprite_height' pixels for the first column/sprite, then the
102/// second column/sprite, etc.
103typedef struct Ss_Row {
104 uint16_t num_cols; /// Number of columns in this row.
105 Pixel pixels[1]; /// Count: num_cols * sprite_width * sprite_height.
106} Ss_Row;
107
108/// Sprite sheet top-level data definition.
109///
110/// Sprite width and height are assumed constant throughout the sprite sheet.
111typedef struct Ss_SpriteSheet {
112 uint16_t sprite_width; /// Sprite width in pixels.
113 uint16_t sprite_height; /// Sprite height in pixels.
114 uint16_t num_rows;
115 Ss_Row rows[1]; /// Count: num_rows.
116} Ss_SpriteSheet;
117
118const Ss_Row* get_sprite_sheet_row(const Ss_SpriteSheet* sheet, int row) {
119 assert(sheet);
120 assert(row >= 0);
121 assert(row < sheet->num_rows);
122 return &sheet->rows[row];
123}
124
125// -----------------------------------------------------------------------------
73// Renderer state. 126// Renderer state.
74// ----------------------------------------------------------------------------- 127// -----------------------------------------------------------------------------
75 128
@@ -79,34 +132,45 @@ typedef struct TileData {
79 uint16_t pixels_handle; // Handle to the tile's pixels in the pixel pool. 132 uint16_t pixels_handle; // Handle to the tile's pixels in the pixel pool.
80} TileData; 133} TileData;
81 134
135// File format is already convenient for working in memory.
136typedef Ss_Row SpriteSheetRow;
137typedef Ss_SpriteSheet SpriteSheetData;
138
139typedef struct SpriteData {
140 SpriteSheet sheet; // Handle to the sprite's sheet.
141 ivec2 position;
142 int animation; // Current animation.
143 int frame; // Current frame of animation.
144} SpriteData;
145
82DEF_MEMPOOL_DYN(TilePool, TileData) 146DEF_MEMPOOL_DYN(TilePool, TileData)
83DEF_MEM_DYN(PixelPool, Pixel) 147DEF_MEM_DYN(PixelPool, Pixel)
84 148
149DEF_MEMPOOL_DYN(SpritePool, SpriteData)
150DEF_MEM_DYN(SpriteSheetPool, SpriteSheetData)
151
85typedef struct IsoGfx { 152typedef struct IsoGfx {
86 int screen_width; 153 int screen_width;
87 int screen_height; 154 int screen_height;
88 int tile_width; 155 int tile_width;
89 int tile_height; 156 int tile_height;
90 int world_width; 157 int world_width;
91 int world_height; 158 int world_height;
92 Tile* world; 159 int max_num_sprites;
93 Pixel* screen; 160 int sprite_sheet_pool_size_bytes;
94 TilePool tiles; 161 double last_animation_time;
95 PixelPool pixels; 162 Tile* world;
163 Pixel* screen;
164 TilePool tiles;
165 PixelPool pixels;
166 SpritePool sprites;
167 SpriteSheetPool sheets;
96} IsoGfx; 168} IsoGfx;
97 169
98// ----------------------------------------------------------------------------- 170// -----------------------------------------------------------------------------
99// Math and world / tile / screen access. 171// Math and world / tile / screen access.
100// ----------------------------------------------------------------------------- 172// -----------------------------------------------------------------------------
101 173
102typedef struct ivec2 {
103 int x, y;
104} ivec2;
105
106typedef struct vec2 {
107 double x, y;
108} vec2;
109
110static inline ivec2 ivec2_add(ivec2 a, ivec2 b) { 174static inline ivec2 ivec2_add(ivec2 a, ivec2 b) {
111 return (ivec2){.x = a.x + b.x, .y = a.y + b.y}; 175 return (ivec2){.x = a.x + b.x, .y = a.y + b.y};
112} 176}
@@ -220,8 +284,15 @@ IsoGfx* isogfx_new(const IsoGfxDesc* desc) {
220 iso->screen_width = desc->screen_width; 284 iso->screen_width = desc->screen_width;
221 iso->screen_height = desc->screen_height; 285 iso->screen_height = desc->screen_height;
222 286
223 const int screen_size = desc->screen_width * desc->screen_height; 287 iso->last_animation_time = 0.0;
288
289 iso->max_num_sprites = desc->max_num_sprites == 0 ? DEFAULT_MAX_NUM_SPRITES
290 : desc->max_num_sprites;
291 iso->sprite_sheet_pool_size_bytes = desc->sprite_sheet_pool_size_bytes == 0
292 ? DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES
293 : desc->sprite_sheet_pool_size_bytes;
224 294
295 const int screen_size = desc->screen_width * desc->screen_height;
225 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { 296 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) {
226 goto cleanup; 297 goto cleanup;
227 } 298 }
@@ -233,7 +304,7 @@ cleanup:
233 return 0; 304 return 0;
234} 305}
235 306
236/// Destroy the world and its tile set. 307/// Destroy the world, its tile set, and the underlying pools.
237static void destroy_world(IsoGfx* iso) { 308static void destroy_world(IsoGfx* iso) {
238 assert(iso); 309 assert(iso);
239 if (iso->world) { 310 if (iso->world) {
@@ -244,11 +315,19 @@ static void destroy_world(IsoGfx* iso) {
244 mem_del(&iso->pixels); 315 mem_del(&iso->pixels);
245} 316}
246 317
318/// Destroy all loaded sprites and the underlying pools.
319static void destroy_sprites(IsoGfx* iso) {
320 assert(iso);
321 mempool_del(&iso->sprites);
322 mem_del(&iso->sheets);
323}
324
247void isogfx_del(IsoGfx** pIso) { 325void isogfx_del(IsoGfx** pIso) {
248 assert(pIso); 326 assert(pIso);
249 IsoGfx* iso = *pIso; 327 IsoGfx* iso = *pIso;
250 if (iso) { 328 if (iso) {
251 destroy_world(iso); 329 destroy_world(iso);
330 destroy_sprites(iso);
252 if (iso->screen) { 331 if (iso->screen) {
253 free(iso->screen); 332 free(iso->screen);
254 iso->screen = 0; 333 iso->screen = 0;
@@ -341,7 +420,7 @@ bool isogfx_load_world(IsoGfx* iso, const char* filepath) {
341 // Tile set path is relative to the tile map file. Make it relative to the 420 // Tile set path is relative to the tile map file. Make it relative to the
342 // current working directory before loading. 421 // current working directory before loading.
343 char ts_path_cwd[PATH_MAX] = {0}; 422 char ts_path_cwd[PATH_MAX] = {0};
344 if (!make_relative_path(MAX_PATH_LENGTH, filepath, ts_path, ts_path_cwd)) { 423 if (!make_relative_path(filepath, ts_path, ts_path_cwd, PATH_MAX)) {
345 goto cleanup; 424 goto cleanup;
346 } 425 }
347 426
@@ -498,36 +577,199 @@ void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) {
498 } 577 }
499} 578}
500 579
580bool isogfx_load_sprite_sheet(
581 IsoGfx* iso, const char* filepath, SpriteSheet* p_sheet) {
582 assert(iso);
583 assert(filepath);
584 assert(p_sheet);
585
586 bool success = false;
587
588 // Lazy initialization of sprite pools.
589 if (mempool_capacity(&iso->sprites) == 0) {
590 if (!mempool_make_dyn(
591 &iso->sprites, iso->max_num_sprites, sizeof(SpriteData))) {
592 return false;
593 }
594 }
595 if (mem_capacity(&iso->sheets) == 0) {
596 // Using a block size of 1 byte for sprite sheet data.
597 if (!mem_make_dyn(&iso->sheets, iso->sprite_sheet_pool_size_bytes, 1)) {
598 return false;
599 }
600 }
601
602 // Load sprite sheet file.
603 printf("Load sprite sheet: %s\n", filepath);
604 FILE* file = fopen(filepath, "rb");
605 if (file == NULL) {
606 goto cleanup;
607 }
608 const size_t sheet_size = get_file_size(file);
609 SpriteSheetData* ss_sheet = mem_alloc(&iso->sheets, sheet_size);
610 if (!ss_sheet) {
611 goto cleanup;
612 }
613 if (fread(ss_sheet, sheet_size, 1, file) != 1) {
614 goto cleanup;
615 }
616
617 *p_sheet = mem_get_chunk_handle(&iso->sheets, ss_sheet);
618 success = true;
619
620cleanup:
621 // Pools remain initialized since client may attempt to load other sprites.
622 if (file != NULL) {
623 fclose(file);
624 }
625 if (!success) {
626 if (ss_sheet) {
627 mem_free(&iso->sheets, &ss_sheet);
628 }
629 }
630 return success;
631}
632
633Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) {
634 assert(iso);
635
636 SpriteData* sprite = mempool_alloc(&iso->sprites);
637 assert(sprite);
638
639 sprite->sheet = sheet;
640
641 return mempool_get_block_index(&iso->sprites, sprite);
642}
643
644#define with_sprite(SPRITE, BODY) \
645 { \
646 SpriteData* data = mempool_get_block(&iso->sprites, sprite); \
647 assert(data); \
648 BODY; \
649 }
650
651void isogfx_set_sprite_position(IsoGfx* iso, Sprite sprite, int x, int y) {
652 assert(iso);
653 with_sprite(sprite, {
654 data->position.x = x;
655 data->position.y = y;
656 });
657}
658
659void isogfx_set_sprite_animation(IsoGfx* iso, Sprite sprite, int animation) {
660 assert(iso);
661 with_sprite(sprite, { data->animation = animation; });
662}
663
664void isogfx_update(IsoGfx* iso, double t) {
665 assert(iso);
666
667 // If this is the first time update() is called after initialization, just
668 // record the starting animation time.
669 if (iso->last_animation_time == 0.0) {
670 iso->last_animation_time = t;
671 return;
672 }
673
674 if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) {
675 // TODO: Consider linking animated sprites in a list so that we only walk
676 // over those here and not also the static sprites.
677 mempool_foreach(&iso->sprites, sprite, {
678 const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet);
679 assert(sheet); // TODO: Make this a hard assert inside the mem/pool.
680 const SpriteSheetRow* row =
681 get_sprite_sheet_row(sheet, sprite->animation);
682 sprite->frame = (sprite->frame + 1) % row->num_cols;
683 });
684
685 iso->last_animation_time = t;
686 }
687}
688
501// ----------------------------------------------------------------------------- 689// -----------------------------------------------------------------------------
502// Rendering and picking. 690// Rendering and picking.
503// ----------------------------------------------------------------------------- 691// -----------------------------------------------------------------------------
504 692
505static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { 693typedef struct CoordSystem {
694 ivec2 o; /// Origin.
695 ivec2 x;
696 ivec2 y;
697} CoordSystem;
698
699/// Create the basis for the isometric coordinate system with origin and vectors
700/// expressed in the Cartesian system.
701static CoordSystem make_iso_coord_system(const IsoGfx* iso) {
506 assert(iso); 702 assert(iso);
703 // const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0};
704 const ivec2 o = {
705 (iso->screen_width / 2) - (iso->tile_width / 2), iso->tile_height};
706 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
707 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
708 return (CoordSystem){o, x, y};
709}
507 710
508 const TileData* tile_data = mempool_get_block(&iso->tiles, tile); 711static Pixel alpha_blend(Pixel src, Pixel dst) {
509 assert(tile_data); 712 if ((src.a == 255) || (dst.a == 0)) {
713 return src;
714 }
715 const uint16_t one_minus_alpha = 255 - src.a;
716#define blend(s, d) \
717 (Channel)( \
718 (double)((uint16_t)s * (uint16_t)src.a + \
719 (uint16_t)d * one_minus_alpha) / \
720 255.0)
721 return (Pixel){
722 .r = blend(src.r, dst.r),
723 .g = blend(src.g, dst.g),
724 .b = blend(src.b, dst.b),
725 .a = src.a};
726}
727
728/// Draw a rectangle (tile or sprite).
729///
730/// The rectangle's bottom-left corner is mapped to the given origin. The
731/// rectangle then extends to the right and top of the origin.
732///
733/// The rectangle's pixels are assumed to be arranged in a linear, row-major
734/// fashion.
735static void draw_rect(
736 IsoGfx* iso, ivec2 origin, int rect_width, int rect_height,
737 const Pixel* pixels) {
738 assert(iso);
510 739
511 // Tile can exceed screen bounds, so we must clip it. 740 // Rect can exceed screen bounds, so we must clip it.
512#define max(a, b) (a > b ? a : b) 741#define max(a, b) (a > b ? a : b)
513 const int py_offset = max(0, (int)tile_data->height - origin.y); 742 const int py_offset = max(0, rect_height - origin.y);
514 origin.y = max(0, origin.y - (int)tile_data->height); 743 origin.y = max(0, origin.y - rect_height);
515 744
516 // Clip along Y and X as we draw. 745 // Clip along Y and X as we draw.
517 for (int py = py_offset; 746 for (int py = py_offset;
518 (py < tile_data->height) && (origin.y + py < iso->screen_height); ++py) { 747 (py < rect_height) && (origin.y + py < iso->screen_height); ++py) {
519 const int sy = origin.y + py - py_offset; 748 const int sy = origin.y + py - py_offset;
520 for (int px = 0; 749 for (int px = 0; (px < rect_width) && (origin.x + px < iso->screen_width);
521 (px < tile_data->width) && (origin.x + px < iso->screen_width); ++px) { 750 ++px) {
522 const Pixel colour = tile_xy(iso, tile_data, px, py); 751 const Pixel colour = pixels[py * rect_width + px];
523 if (colour.a > 0) { 752 if (colour.a > 0) {
524 const int sx = origin.x + px; 753 const int sx = origin.x + px;
525 *screen_xy_mut(iso, sx, sy) = colour; 754 const Pixel dst = screen_xy(iso, sx, sy);
755 const Pixel final = alpha_blend(colour, dst);
756 *screen_xy_mut(iso, sx, sy) = final;
526 } 757 }
527 } 758 }
528 } 759 }
529} 760}
530 761
762static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) {
763 assert(iso);
764
765 const TileData* tile_data = mempool_get_block(&iso->tiles, tile);
766 assert(tile_data);
767
768 const Pixel* pixels = tile_xy_const_ref(iso, tile_data, 0, 0);
769
770 draw_rect(iso, origin, tile_data->width, tile_data->height, pixels);
771}
772
531static void draw(IsoGfx* iso) { 773static void draw(IsoGfx* iso) {
532 assert(iso); 774 assert(iso);
533 775
@@ -536,11 +778,7 @@ static void draw(IsoGfx* iso) {
536 778
537 memset(iso->screen, 0, W * H * sizeof(Pixel)); 779 memset(iso->screen, 0, W * H * sizeof(Pixel));
538 780
539 // const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; 781 const CoordSystem iso_space = make_iso_coord_system(iso);
540 const ivec2 o = {
541 (iso->screen_width / 2) - (iso->tile_width / 2), iso->tile_height};
542 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
543 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
544 782
545 // TODO: Culling. 783 // TODO: Culling.
546 // Ex: map the screen corners to tile space to cull. 784 // Ex: map the screen corners to tile space to cull.
@@ -550,16 +788,58 @@ static void draw(IsoGfx* iso) {
550 for (int ty = 0; ty < iso->world_height; ++ty) { 788 for (int ty = 0; ty < iso->world_height; ++ty) {
551 for (int tx = 0; tx < iso->world_width; ++tx) { 789 for (int tx = 0; tx < iso->world_width; ++tx) {
552 const Tile tile = world_xy(iso, tx, ty); 790 const Tile tile = world_xy(iso, tx, ty);
553 const ivec2 so = 791 const ivec2 so = ivec2_add(
554 ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty))); 792 iso_space.o,
793 ivec2_add(
794 ivec2_scale(iso_space.x, tx), ivec2_scale(iso_space.y, ty)));
555 draw_tile(iso, so, tile); 795 draw_tile(iso, so, tile);
556 } 796 }
557 } 797 }
558} 798}
559 799
800static void draw_sprite(
801 IsoGfx* iso, ivec2 origin, const SpriteData* sprite,
802 const SpriteSheetData* sheet) {
803 assert(iso);
804 assert(sprite);
805 assert(sheet);
806 assert(sprite->animation >= 0);
807 assert(sprite->animation < sheet->num_rows);
808 assert(sprite->frame >= 0);
809
810 const SpriteSheetRow* ss_row = &sheet->rows[sprite->animation];
811 assert(sprite->frame < ss_row->num_cols);
812
813 const int sprite_offset =
814 sprite->frame * sheet->sprite_width * sheet->sprite_height;
815
816 const Pixel* frame = &ss_row->pixels[sprite_offset];
817
818 draw_rect(iso, origin, sheet->sprite_width, sheet->sprite_height, frame);
819}
820
821static void draw_sprites(IsoGfx* iso) {
822 assert(iso);
823
824 const CoordSystem iso_space = make_iso_coord_system(iso);
825
826 mempool_foreach(&iso->sprites, sprite, {
827 const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet);
828 assert(sheet);
829
830 const ivec2 so = ivec2_add(
831 iso_space.o, ivec2_add(
832 ivec2_scale(iso_space.x, sprite->position.x),
833 ivec2_scale(iso_space.y, sprite->position.y)));
834
835 draw_sprite(iso, so, sprite, sheet);
836 });
837}
838
560void isogfx_render(IsoGfx* iso) { 839void isogfx_render(IsoGfx* iso) {
561 assert(iso); 840 assert(iso);
562 draw(iso); 841 draw(iso);
842 draw_sprites(iso);
563} 843}
564 844
565void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) { 845void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) {