diff options
Diffstat (limited to 'src/gfx2d.c')
-rw-r--r-- | src/gfx2d.c | 109 |
1 files changed, 82 insertions, 27 deletions
diff --git a/src/gfx2d.c b/src/gfx2d.c index 266a5f7..1c8b06f 100644 --- a/src/gfx2d.c +++ b/src/gfx2d.c | |||
@@ -20,6 +20,12 @@ | |||
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 | |||
23 | typedef struct ivec2 { | 29 | typedef struct ivec2 { |
24 | int x, y; | 30 | int x, y; |
25 | } ivec2; | 31 | } ivec2; |
@@ -73,6 +79,10 @@ static inline ivec2 ivec2_add(ivec2 a, ivec2 b) { | |||
73 | return (ivec2){.x = a.x + b.x, .y = a.y + b.y}; | 79 | return (ivec2){.x = a.x + b.x, .y = a.y + b.y}; |
74 | } | 80 | } |
75 | 81 | ||
82 | static inline ivec2 ivec2_mul(ivec2 a, ivec2 b) { | ||
83 | return (ivec2){.x = a.x * b.x, .y = a.y * b.y}; | ||
84 | } | ||
85 | |||
76 | static inline ivec2 ivec2_scale(ivec2 a, int s) { | 86 | static inline ivec2 ivec2_scale(ivec2 a, int s) { |
77 | return (ivec2){.x = a.x * s, .y = a.y * s}; | 87 | return (ivec2){.x = a.x * s, .y = a.y * s}; |
78 | } | 88 | } |
@@ -85,12 +95,14 @@ static inline vec2 vec2_add(vec2 a, vec2 b) { | |||
85 | 95 | ||
86 | static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } | 96 | static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } |
87 | 97 | ||
88 | /// Map map coordinates to screen coordinates, both Cartesian. | 98 | /// Map ortho coordinates to screen coordinates. |
89 | static ivec2 map2screen( | 99 | /// |
100 | /// Camera coordinates are in pixels. Map coordinates are in tiles. | ||
101 | static ivec2 ortho2screen( | ||
90 | ivec2 camera, int tile_width, int tile_height, int map_x, int map_y) { | 102 | ivec2 camera, int tile_width, int tile_height, int map_x, int map_y) { |
91 | return ivec2_add( | 103 | return ivec2_add( |
92 | ivec2_neg(camera), | 104 | ivec2_neg(camera), |
93 | (ivec2){.x = map_x * tile_width, .y = map_y * tile_height}); | 105 | ivec2_mul((ivec2){map_x, map_y}, (ivec2){tile_width, tile_height})); |
94 | } | 106 | } |
95 | 107 | ||
96 | // Not actually used because we pre-compute the two axis vectors instead. | 108 | // Not actually used because we pre-compute the two axis vectors instead. |
@@ -144,8 +156,8 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { | |||
144 | const double one_over_s = 1. / (double)s; | 156 | const double one_over_s = 1. / (double)s; |
145 | const double one_over_t = 1. / (double)t; | 157 | const double one_over_t = 1. / (double)t; |
146 | const double x = cart.x - (double)(w / 2); | 158 | const double x = cart.x - (double)(w / 2); |
147 | return (vec2){.x = (one_over_s * x + one_over_t * cart.y), | 159 | return (vec2){.x = ((one_over_s * x) + (one_over_t * cart.y)), |
148 | .y = (-one_over_s * x + one_over_t * cart.y)}; | 160 | .y = ((-one_over_s * x) + (one_over_t * cart.y))}; |
149 | } | 161 | } |
150 | 162 | ||
151 | static inline const Pixel* screen_xy_const_ref( | 163 | static inline const Pixel* screen_xy_const_ref( |
@@ -155,10 +167,10 @@ static inline const Pixel* screen_xy_const_ref( | |||
155 | assert(y >= 0); | 167 | assert(y >= 0); |
156 | assert(x < screen->width); | 168 | assert(x < screen->width); |
157 | assert(y < screen->height); | 169 | assert(y < screen->height); |
158 | return &screen->pixels[y * screen->width + x]; | 170 | return &screen->pixels[(y * screen->width) + x]; |
159 | } | 171 | } |
160 | 172 | ||
161 | static inline Pixel screen_xy(Screen* screen, int x, int y) { | 173 | static inline Pixel screen_xy(const Screen* screen, int x, int y) { |
162 | return *screen_xy_const_ref(screen, x, y); | 174 | return *screen_xy_const_ref(screen, x, y); |
163 | } | 175 | } |
164 | 176 | ||
@@ -263,6 +275,8 @@ void gfx2d_make_map(Gfx2d* gfx, const MapDesc* desc) { | |||
263 | .base_tile_width = desc->tile_width, | 275 | .base_tile_width = desc->tile_width, |
264 | .base_tile_height = desc->tile_height, | 276 | .base_tile_height = desc->tile_height, |
265 | .num_layers = 1, | 277 | .num_layers = 1, |
278 | .flags = | ||
279 | (desc->orientation == MapOrthogonal) ? Tm_Orthogonal : Tm_Isometric, | ||
266 | }; | 280 | }; |
267 | 281 | ||
268 | gfx->tileset = memstack_alloc_aligned(&gfx->stack, tileset_size_bytes, 4); | 282 | gfx->tileset = memstack_alloc_aligned(&gfx->stack, tileset_size_bytes, 4); |
@@ -352,10 +366,10 @@ static void make_tile_from_colour( | |||
352 | const int height = tile->height; | 366 | const int height = tile->height; |
353 | const int r = width / height; | 367 | const int r = width / height; |
354 | for (int y = 0; y < height / 2; ++y) { | 368 | for (int y = 0; y < height / 2; ++y) { |
355 | const int mask_start = width / 2 - r * y - 1; | 369 | const int mask_start = (width / 2) - (r * y) - 1; |
356 | const int mask_end = width / 2 + r * y + 1; | 370 | const int mask_end = (width / 2) + (r * y) + 1; |
357 | for (int x = 0; x < width; ++x) { | 371 | for (int x = 0; x < width; ++x) { |
358 | const bool mask = (mask_start <= x) && (x <= mask_end); | 372 | 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}; | 373 | const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; |
360 | 374 | ||
361 | // Top half. | 375 | // Top half. |
@@ -563,7 +577,6 @@ static void draw_rect( | |||
563 | 577 | ||
564 | // Rect origin can be outside screen bounds, so we must offset accordingly to | 578 | // Rect origin can be outside screen bounds, so we must offset accordingly to |
565 | // draw only the visible portion. | 579 | // draw only the visible portion. |
566 | #define max(a, b) (a > b ? a : b) | ||
567 | const int px_offset = max(0, -top_left.x); | 580 | const int px_offset = max(0, -top_left.x); |
568 | const int py_offset = max(0, -top_left.y); | 581 | const int py_offset = max(0, -top_left.y); |
569 | 582 | ||
@@ -596,7 +609,7 @@ static void draw_tile_ortho(Gfx2d* gfx, Tile tile, int x, int y) { | |||
596 | const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile); | 609 | const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile); |
597 | const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile); | 610 | const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile); |
598 | 611 | ||
599 | const ivec2 screen_origin = map2screen( | 612 | const ivec2 screen_origin = ortho2screen( |
600 | gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, x, y); | 613 | gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, x, y); |
601 | 614 | ||
602 | draw_rect( | 615 | draw_rect( |
@@ -638,14 +651,24 @@ static void draw_map_ortho(Gfx2d* gfx) { | |||
638 | assert(gfx); | 651 | assert(gfx); |
639 | assert(gfx->map); | 652 | assert(gfx->map); |
640 | 653 | ||
641 | // TODO: Same TODOs as in draw_map_iso(). | 654 | // Render the tiles that the camera view rectangle intersects. |
642 | 655 | // +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); | 656 | // multiple of the base tile dimensions. |
644 | 657 | const int x_tiles = gfx->screen.width / gfx->map->base_tile_width; | |
645 | for (int wy = 0; wy < gfx->map->world_height; ++wy) { | 658 | const int y_tiles = gfx->screen.height / gfx->map->base_tile_height; |
646 | for (int wx = 0; wx < gfx->map->world_width; ++wx) { | 659 | const int x0 = gfx->camera.x / gfx->map->base_tile_width; |
647 | const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy); | 660 | const int y0 = gfx->camera.y / gfx->map->base_tile_height; |
648 | draw_tile_ortho(gfx, tile, wx, wy); | 661 | const int x1 = min(gfx->map->world_width, x0 + x_tiles + 1); |
662 | const int y1 = min(gfx->map->world_height, y0 + y_tiles + 1); | ||
663 | |||
664 | for (uint16_t l = 0; l < gfx->map->num_layers; ++l) { | ||
665 | const Tm_Layer* layer = tm_map_get_layer(gfx->map, l); | ||
666 | |||
667 | for (int wy = y0; wy < y1; ++wy) { | ||
668 | for (int wx = x0; wx < x1; ++wx) { | ||
669 | const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy); | ||
670 | draw_tile_ortho(gfx, tile, wx, wy); | ||
671 | } | ||
649 | } | 672 | } |
650 | } | 673 | } |
651 | } | 674 | } |
@@ -704,7 +727,7 @@ static void draw_sprite_ortho( | |||
704 | // Apply an offset similarly to how we offset tiles. The sprite is offset by | 727 | // 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 | 728 | // -base_tile_width/2 along the x-axis to align the sprite with the leftmost |
706 | // edge of the tile it is on. | 729 | // edge of the tile it is on. |
707 | const ivec2 screen_origin = map2screen( | 730 | const ivec2 screen_origin = ortho2screen( |
708 | gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, | 731 | gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, |
709 | sprite->position.x, sprite->position.y); | 732 | sprite->position.x, sprite->position.y); |
710 | 733 | ||
@@ -761,13 +784,11 @@ static void draw_sprites(Gfx2d* gfx) { | |||
761 | } | 784 | } |
762 | } | 785 | } |
763 | 786 | ||
764 | void gfx2d_set_camera(Gfx2d* gfx, int x, int y) { | 787 | 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); | 788 | assert(gfx); |
789 | // Storing the camera mostly for debugging convenience. It could otherwise be | ||
790 | // passed around. | ||
791 | gfx->camera = (ivec2){camera_x, camera_y}; | ||
771 | draw_map(gfx); | 792 | draw_map(gfx); |
772 | draw_sprites(gfx); | 793 | draw_sprites(gfx); |
773 | } | 794 | } |
@@ -787,6 +808,40 @@ void gfx2d_draw_tile(Gfx2d* gfx, int x, int y, Tile tile) { | |||
787 | } | 808 | } |
788 | } | 809 | } |
789 | 810 | ||
811 | static void clip_camera_ortho(const Gfx2d* gfx, float* x, float* y) { | ||
812 | assert(gfx); | ||
813 | assert(gfx->map); | ||
814 | |||
815 | if (x != nullptr) { | ||
816 | const int width_pixels = gfx->map->world_width * gfx->map->base_tile_width; | ||
817 | const int x_max = width_pixels - gfx->screen.width; | ||
818 | *x = min((float)x_max, max(0.F, *x)); | ||
819 | } | ||
820 | if (y != nullptr) { | ||
821 | const int height_pixels = | ||
822 | gfx->map->world_height * gfx->map->base_tile_height; | ||
823 | const int y_max = height_pixels - gfx->screen.height; | ||
824 | *y = min((float)y_max, max(0.F, *y)); | ||
825 | } | ||
826 | } | ||
827 | |||
828 | void gfx2d_clip_camera(const Gfx2d* gfx, float* x, float* y) { | ||
829 | assert(gfx); | ||
830 | assert(gfx->map); | ||
831 | assert(x); | ||
832 | assert(y); | ||
833 | |||
834 | const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags; | ||
835 | switch (flags->orientation) { | ||
836 | case Tm_Orthogonal: | ||
837 | clip_camera_ortho(gfx, x, y); | ||
838 | break; | ||
839 | case Tm_Isometric: | ||
840 | // TODO: Clip camera in isometric maps. | ||
841 | break; | ||
842 | } | ||
843 | } | ||
844 | |||
790 | void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) { | 845 | void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) { |
791 | assert(gfx); | 846 | assert(gfx); |
792 | assert(width); | 847 | assert(width); |