diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gfx2d.c | 145 |
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. | ||
30 | static const Tile NoTile = 0; | ||
31 | |||
23 | typedef struct ivec2 { | 32 | typedef 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 | ||
85 | static inline ivec2 ivec2_mul(ivec2 a, ivec2 b) { | ||
86 | return (ivec2){.x = a.x * b.x, .y = a.y * b.y}; | ||
87 | } | ||
88 | |||
76 | static inline ivec2 ivec2_scale(ivec2 a, int s) { | 89 | static 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 | ||
86 | static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } | 99 | static 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. |
89 | static ivec2 map2screen( | 102 | /// |
103 | /// Camera coordinates are in pixels. Map coordinates are in tiles. | ||
104 | static 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 | ||
151 | static inline const Pixel* screen_xy_const_ref( | 166 | static 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 | ||
161 | static inline Pixel screen_xy(Screen* screen, int x, int y) { | 176 | static 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 | ||
764 | void gfx2d_set_camera(Gfx2d* gfx, int x, int y) { | 799 | void gfx2d_render(Gfx2d* gfx, int camera_x, int camera_y) { |
765 | assert(gfx); | ||
766 | gfx->camera = (ivec2){x, y}; | ||
767 | } | ||
768 | |||
769 | void 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 | ||
823 | static 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 | |||
840 | void 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 | |||
790 | void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) { | 857 | void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) { |
791 | assert(gfx); | 858 | assert(gfx); |
792 | assert(width); | 859 | assert(width); |