summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gfx2d.c145
1 files changed, 106 insertions, 39 deletions
diff --git a/src/gfx2d.c b/src/gfx2d.c
index 266a5f7..eaed2b8 100644
--- a/src/gfx2d.c
+++ b/src/gfx2d.c
@@ -20,6 +20,15 @@
20/// Time between animation updates. 20/// Time between animation updates.
21#define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS) 21#define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS)
22 22
23/// Take the maximum of two values.
24#define max(a, b) ((a) > (b) ? (a) : (b))
25
26/// Take the minimum of two values.
27#define min(a, b) ((a) < (b) ? (a) : (b))
28
29/// The 0-tile denotes "no tile". The renderer skips drawing 0-tiles.
30static const Tile NoTile = 0;
31
23typedef struct ivec2 { 32typedef struct ivec2 {
24 int x, y; 33 int x, y;
25} ivec2; 34} ivec2;
@@ -73,6 +82,10 @@ static inline ivec2 ivec2_add(ivec2 a, ivec2 b) {
73 return (ivec2){.x = a.x + b.x, .y = a.y + b.y}; 82 return (ivec2){.x = a.x + b.x, .y = a.y + b.y};
74} 83}
75 84
85static inline ivec2 ivec2_mul(ivec2 a, ivec2 b) {
86 return (ivec2){.x = a.x * b.x, .y = a.y * b.y};
87}
88
76static inline ivec2 ivec2_scale(ivec2 a, int s) { 89static inline ivec2 ivec2_scale(ivec2 a, int s) {
77 return (ivec2){.x = a.x * s, .y = a.y * s}; 90 return (ivec2){.x = a.x * s, .y = a.y * s};
78} 91}
@@ -85,12 +98,14 @@ static inline vec2 vec2_add(vec2 a, vec2 b) {
85 98
86static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } 99static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; }
87 100
88/// Map map coordinates to screen coordinates, both Cartesian. 101/// Map ortho coordinates to screen coordinates.
89static ivec2 map2screen( 102///
103/// Camera coordinates are in pixels. Map coordinates are in tiles.
104static ivec2 ortho2screen(
90 ivec2 camera, int tile_width, int tile_height, int map_x, int map_y) { 105 ivec2 camera, int tile_width, int tile_height, int map_x, int map_y) {
91 return ivec2_add( 106 return ivec2_add(
92 ivec2_neg(camera), 107 ivec2_neg(camera),
93 (ivec2){.x = map_x * tile_width, .y = map_y * tile_height}); 108 ivec2_mul((ivec2){map_x, map_y}, (ivec2){tile_width, tile_height}));
94} 109}
95 110
96// Not actually used because we pre-compute the two axis vectors instead. 111// Not actually used because we pre-compute the two axis vectors instead.
@@ -144,8 +159,8 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
144 const double one_over_s = 1. / (double)s; 159 const double one_over_s = 1. / (double)s;
145 const double one_over_t = 1. / (double)t; 160 const double one_over_t = 1. / (double)t;
146 const double x = cart.x - (double)(w / 2); 161 const double x = cart.x - (double)(w / 2);
147 return (vec2){.x = (one_over_s * x + one_over_t * cart.y), 162 return (vec2){.x = ((one_over_s * x) + (one_over_t * cart.y)),
148 .y = (-one_over_s * x + one_over_t * cart.y)}; 163 .y = ((-one_over_s * x) + (one_over_t * cart.y))};
149} 164}
150 165
151static inline const Pixel* screen_xy_const_ref( 166static inline const Pixel* screen_xy_const_ref(
@@ -155,10 +170,10 @@ static inline const Pixel* screen_xy_const_ref(
155 assert(y >= 0); 170 assert(y >= 0);
156 assert(x < screen->width); 171 assert(x < screen->width);
157 assert(y < screen->height); 172 assert(y < screen->height);
158 return &screen->pixels[y * screen->width + x]; 173 return &screen->pixels[(y * screen->width) + x];
159} 174}
160 175
161static inline Pixel screen_xy(Screen* screen, int x, int y) { 176static inline Pixel screen_xy(const Screen* screen, int x, int y) {
162 return *screen_xy_const_ref(screen, x, y); 177 return *screen_xy_const_ref(screen, x, y);
163} 178}
164 179
@@ -263,6 +278,8 @@ void gfx2d_make_map(Gfx2d* gfx, const MapDesc* desc) {
263 .base_tile_width = desc->tile_width, 278 .base_tile_width = desc->tile_width,
264 .base_tile_height = desc->tile_height, 279 .base_tile_height = desc->tile_height,
265 .num_layers = 1, 280 .num_layers = 1,
281 .flags =
282 (desc->orientation == MapOrthogonal) ? Tm_Orthogonal : Tm_Isometric,
266 }; 283 };
267 284
268 gfx->tileset = memstack_alloc_aligned(&gfx->stack, tileset_size_bytes, 4); 285 gfx->tileset = memstack_alloc_aligned(&gfx->stack, tileset_size_bytes, 4);
@@ -352,10 +369,10 @@ static void make_tile_from_colour(
352 const int height = tile->height; 369 const int height = tile->height;
353 const int r = width / height; 370 const int r = width / height;
354 for (int y = 0; y < height / 2; ++y) { 371 for (int y = 0; y < height / 2; ++y) {
355 const int mask_start = width / 2 - r * y - 1; 372 const int mask_start = (width / 2) - (r * y) - 1;
356 const int mask_end = width / 2 + r * y + 1; 373 const int mask_end = (width / 2) + (r * y) + 1;
357 for (int x = 0; x < width; ++x) { 374 for (int x = 0; x < width; ++x) {
358 const bool mask = (mask_start <= x) && (x <= mask_end); 375 const bool mask = ((mask_start <= x) && (x <= mask_end)) != 0;
359 const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; 376 const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0};
360 377
361 // Top half. 378 // Top half.
@@ -563,7 +580,6 @@ static void draw_rect(
563 580
564 // Rect origin can be outside screen bounds, so we must offset accordingly to 581 // Rect origin can be outside screen bounds, so we must offset accordingly to
565 // draw only the visible portion. 582 // draw only the visible portion.
566#define max(a, b) (a > b ? a : b)
567 const int px_offset = max(0, -top_left.x); 583 const int px_offset = max(0, -top_left.x);
568 const int py_offset = max(0, -top_left.y); 584 const int py_offset = max(0, -top_left.y);
569 585
@@ -593,10 +609,14 @@ static void draw_tile_ortho(Gfx2d* gfx, Tile tile, int x, int y) {
593 assert(x < gfx->map->world_width); 609 assert(x < gfx->map->world_width);
594 assert(y < gfx->map->world_height); 610 assert(y < gfx->map->world_height);
595 611
612 if (tile == NoTile) {
613 return;
614 }
615
596 const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile); 616 const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile);
597 const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile); 617 const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile);
598 618
599 const ivec2 screen_origin = map2screen( 619 const ivec2 screen_origin = ortho2screen(
600 gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, x, y); 620 gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, x, y);
601 621
602 draw_rect( 622 draw_rect(
@@ -613,6 +633,10 @@ static void draw_tile_iso(Gfx2d* gfx, Tile tile, int iso_x, int iso_y) {
613 assert(iso_x < gfx->map->world_width); 633 assert(iso_x < gfx->map->world_width);
614 assert(iso_y < gfx->map->world_height); 634 assert(iso_y < gfx->map->world_height);
615 635
636 if (tile == NoTile) {
637 return;
638 }
639
616 const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile); 640 const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile);
617 const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile); 641 const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile);
618 642
@@ -638,14 +662,24 @@ static void draw_map_ortho(Gfx2d* gfx) {
638 assert(gfx); 662 assert(gfx);
639 assert(gfx->map); 663 assert(gfx->map);
640 664
641 // TODO: Same TODOs as in draw_map_iso(). 665 // Render the tiles that the camera view rectangle intersects.
642 666 // +1 when computing x1,y1 because the screen dimensions need not be a
643 const Tm_Layer* layer = tm_map_get_layer(gfx->map, 0); 667 // multiple of the base tile dimensions.
644 668 const int x_tiles = gfx->screen.width / gfx->map->base_tile_width;
645 for (int wy = 0; wy < gfx->map->world_height; ++wy) { 669 const int y_tiles = gfx->screen.height / gfx->map->base_tile_height;
646 for (int wx = 0; wx < gfx->map->world_width; ++wx) { 670 const int x0 = gfx->camera.x / gfx->map->base_tile_width;
647 const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy); 671 const int y0 = gfx->camera.y / gfx->map->base_tile_height;
648 draw_tile_ortho(gfx, tile, wx, wy); 672 const int x1 = min(gfx->map->world_width, x0 + x_tiles + 1);
673 const int y1 = min(gfx->map->world_height, y0 + y_tiles + 1);
674
675 for (uint16_t l = 0; l < gfx->map->num_layers; ++l) {
676 const Tm_Layer* layer = tm_map_get_layer(gfx->map, l);
677
678 for (int wy = y0; wy < y1; ++wy) {
679 for (int wx = x0; wx < x1; ++wx) {
680 const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy);
681 draw_tile_ortho(gfx, tile, wx, wy);
682 }
649 } 683 }
650 } 684 }
651} 685}
@@ -654,18 +688,19 @@ static void draw_map_iso(Gfx2d* gfx) {
654 assert(gfx); 688 assert(gfx);
655 assert(gfx->map); 689 assert(gfx->map);
656 690
657 // TODO: Support for multiple layers. 691 for (uint16_t l = 0; l < gfx->map->num_layers; ++l) {
658 const Tm_Layer* layer = tm_map_get_layer(gfx->map, 0); 692 const Tm_Layer* layer = tm_map_get_layer(gfx->map, l);
659 693
660 // TODO: Culling. 694 // TODO: Culling.
661 // Ex: map the screen corners to tile space to cull. 695 // Ex: map the screen corners to tile space to cull.
662 // Ex: walk in screen space and fetch the tile. 696 // Ex: walk in screen space and fetch the tile.
663 // The tile-centric approach might be more cache-friendly since the 697 // The tile-centric approach might be more cache-friendly since the
664 // screen-centric approach would juggle multiple tiles throughout the scan. 698 // screen-centric approach would juggle multiple tiles throughout the scan.
665 for (int wy = 0; wy < gfx->map->world_height; ++wy) { 699 for (int wy = 0; wy < gfx->map->world_height; ++wy) {
666 for (int wx = 0; wx < gfx->map->world_width; ++wx) { 700 for (int wx = 0; wx < gfx->map->world_width; ++wx) {
667 const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy); 701 const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy);
668 draw_tile_iso(gfx, tile, wx, wy); 702 draw_tile_iso(gfx, tile, wx, wy);
703 }
669 } 704 }
670 } 705 }
671} 706}
@@ -704,7 +739,7 @@ static void draw_sprite_ortho(
704 // Apply an offset similarly to how we offset tiles. The sprite is offset by 739 // Apply an offset similarly to how we offset tiles. The sprite is offset by
705 // -base_tile_width/2 along the x-axis to align the sprite with the leftmost 740 // -base_tile_width/2 along the x-axis to align the sprite with the leftmost
706 // edge of the tile it is on. 741 // edge of the tile it is on.
707 const ivec2 screen_origin = map2screen( 742 const ivec2 screen_origin = ortho2screen(
708 gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, 743 gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height,
709 sprite->position.x, sprite->position.y); 744 sprite->position.x, sprite->position.y);
710 745
@@ -761,13 +796,11 @@ static void draw_sprites(Gfx2d* gfx) {
761 } 796 }
762} 797}
763 798
764void gfx2d_set_camera(Gfx2d* gfx, int x, int y) { 799void gfx2d_render(Gfx2d* gfx, int camera_x, int camera_y) {
765 assert(gfx);
766 gfx->camera = (ivec2){x, y};
767}
768
769void gfx2d_render(Gfx2d* gfx) {
770 assert(gfx); 800 assert(gfx);
801 // Storing the camera mostly for debugging convenience. It could otherwise be
802 // passed around.
803 gfx->camera = (ivec2){camera_x, camera_y};
771 draw_map(gfx); 804 draw_map(gfx);
772 draw_sprites(gfx); 805 draw_sprites(gfx);
773} 806}
@@ -787,6 +820,40 @@ void gfx2d_draw_tile(Gfx2d* gfx, int x, int y, Tile tile) {
787 } 820 }
788} 821}
789 822
823static void clip_camera_ortho(const Gfx2d* gfx, float* x, float* y) {
824 assert(gfx);
825 assert(gfx->map);
826
827 if (x != nullptr) {
828 const int width_pixels = gfx->map->world_width * gfx->map->base_tile_width;
829 const int x_max = width_pixels - gfx->screen.width;
830 *x = min((float)x_max, max(0.F, *x));
831 }
832 if (y != nullptr) {
833 const int height_pixels =
834 gfx->map->world_height * gfx->map->base_tile_height;
835 const int y_max = height_pixels - gfx->screen.height;
836 *y = min((float)y_max, max(0.F, *y));
837 }
838}
839
840void gfx2d_clip_camera(const Gfx2d* gfx, float* x, float* y) {
841 assert(gfx);
842 assert(gfx->map);
843 assert(x);
844 assert(y);
845
846 const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags;
847 switch (flags->orientation) {
848 case Tm_Orthogonal:
849 clip_camera_ortho(gfx, x, y);
850 break;
851 case Tm_Isometric:
852 // TODO: Clip camera in isometric maps.
853 break;
854 }
855}
856
790void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) { 857void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) {
791 assert(gfx); 858 assert(gfx);
792 assert(width); 859 assert(width);