summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--demos/checkerboard/checkerboard.c5
-rw-r--r--demos/isomap/isomap.c6
-rw-r--r--include/isogfx/asset.h16
-rw-r--r--include/isogfx/gfx2d.h35
-rw-r--r--src/gfx2d.c109
5 files changed, 119 insertions, 52 deletions
diff --git a/demos/checkerboard/checkerboard.c b/demos/checkerboard/checkerboard.c
index 467da61..f9631d8 100644
--- a/demos/checkerboard/checkerboard.c
+++ b/demos/checkerboard/checkerboard.c
@@ -86,7 +86,8 @@ static bool init(GfxApp* app, GfxAppState* state, int argc, const char** argv) {
86 .tile_height = TILE_HEIGHT, 86 .tile_height = TILE_HEIGHT,
87 .world_width = WORLD_WIDTH, 87 .world_width = WORLD_WIDTH,
88 .world_height = WORLD_HEIGHT, 88 .world_height = WORLD_HEIGHT,
89 .num_tiles = NUM_TILES}); 89 .num_tiles = NUM_TILES,
90 .orientation = MapIsometric});
90 91
91 const Tile black = gfx2d_make_tile(iso, &tile_set[Black]); 92 const Tile black = gfx2d_make_tile(iso, &tile_set[Black]);
92 const Tile white = gfx2d_make_tile(iso, &tile_set[White]); 93 const Tile white = gfx2d_make_tile(iso, &tile_set[White]);
@@ -136,7 +137,7 @@ static void render(const GfxApp* app, GfxAppState* state) {
136 137
137 Gfx2d* iso = state->gfx; 138 Gfx2d* iso = state->gfx;
138 139
139 gfx2d_render(iso); 140 gfx2d_render(iso, 0, 0);
140 141
141 if ((state->xpick != -1) && (state->ypick != -1)) { 142 if ((state->xpick != -1) && (state->ypick != -1)) {
142 gfx2d_draw_tile(iso, state->xpick, state->ypick, state->red); 143 gfx2d_draw_tile(iso, state->xpick, state->ypick, state->red);
diff --git a/demos/isomap/isomap.c b/demos/isomap/isomap.c
index 471ef57..e66d14d 100644
--- a/demos/isomap/isomap.c
+++ b/demos/isomap/isomap.c
@@ -47,7 +47,7 @@ static bool init(GfxApp* app, GfxAppState* state, int argc, const char** argv) {
47 Gfx2d* iso = state->gfx; 47 Gfx2d* iso = state->gfx;
48 48
49 if (!gfx2d_load_map( 49 if (!gfx2d_load_map(
50 iso, "/home/jeanne/Nextcloud/assets/tilemaps/scrabling1.tm")) { 50 iso, "/home/jeanne/Nextcloud/assets/tilemaps/desert1.tm")) {
51 return false; 51 return false;
52 } 52 }
53 53
@@ -100,9 +100,9 @@ static void update(GfxApp* app, GfxAppState* state, double t, double dt) {
100 assert(state); 100 assert(state);
101 101
102 state->camera = vec2_add(state->camera, get_camera_movement(app, (R)dt)); 102 state->camera = vec2_add(state->camera, get_camera_movement(app, (R)dt));
103 gfx2d_clip_camera(state->gfx, &state->camera.x, &state->camera.y);
103 104
104 Gfx2d* iso = state->gfx; 105 Gfx2d* iso = state->gfx;
105 gfx2d_set_camera(iso, (int)state->camera.x, (int)state->camera.y);
106 gfx2d_update(iso, t); 106 gfx2d_update(iso, t);
107} 107}
108 108
@@ -111,7 +111,7 @@ static void render(const GfxApp* app, GfxAppState* state) {
111 assert(state); 111 assert(state);
112 112
113 Gfx2d* iso = state->gfx; 113 Gfx2d* iso = state->gfx;
114 gfx2d_render(iso); 114 gfx2d_render(iso, (int)state->camera.x, (int)state->camera.y);
115 gfx2d_backend_render(state->backend, iso); 115 gfx2d_backend_render(state->backend, iso);
116} 116}
117 117
diff --git a/include/isogfx/asset.h b/include/isogfx/asset.h
index 361ffcd..74063a8 100644
--- a/include/isogfx/asset.h
+++ b/include/isogfx/asset.h
@@ -10,6 +10,7 @@
10#include <isogfx/types.h> 10#include <isogfx/types.h>
11 11
12#include <assert.h> 12#include <assert.h>
13#include <stddef.h>
13#include <stdint.h> 14#include <stdint.h>
14 15
15// Maximum length of path strings in .TS and .TM files. 16// Maximum length of path strings in .TS and .TM files.
@@ -154,7 +155,7 @@ static inline const Pixel* ts_tile_get_pixels(
154} 155}
155 156
156/// Return the tile's pixel at (x,y). 157/// Return the tile's pixel at (x,y).
157static const Pixel* ts_tile_xy( 158static inline const Pixel* ts_tile_xy(
158 const Pixel* tile_pixels, const Ts_Tile* tile, int x, int y) { 159 const Pixel* tile_pixels, const Ts_Tile* tile, int x, int y) {
159 assert(tile_pixels); 160 assert(tile_pixels);
160 assert(tile); 161 assert(tile);
@@ -166,9 +167,9 @@ static const Pixel* ts_tile_xy(
166} 167}
167 168
168/// Return the tile's pixel at (x,y). 169/// Return the tile's pixel at (x,y).
169static Pixel* ts_tile_xy_mut( 170static inline Pixel* ts_tile_xy_mut(
170 const Pixel* pixels, const Ts_Tile* tile, int x, int y) { 171 Pixel* tile_pixels, const Ts_Tile* tile, int x, int y) {
171 return (Pixel*)ts_tile_xy(pixels, tile, x, y); 172 return (Pixel*)ts_tile_xy(tile_pixels, tile, x, y);
172} 173}
173 174
174/// Return the ith layer in the tile map. 175/// Return the ith layer in the tile map.
@@ -177,9 +178,10 @@ static inline const Tm_Layer* tm_map_get_layer(
177 assert(map); 178 assert(map);
178 assert(layer >= 0); 179 assert(layer >= 0);
179 assert(layer < map->num_layers); 180 assert(layer < map->num_layers);
180 return (const Tm_Layer*)((const uint8_t*)map->layers + 181 return (
181 (layer * map->world_width * map->world_height * 182 const Tm_Layer*)((const uint8_t*)map->layers +
182 sizeof(Tile))); 183 ((size_t)(layer * map->world_width * map->world_height) *
184 sizeof(Tile)));
183} 185}
184 186
185/// Return the ith layer in the tile map. 187/// Return the ith layer in the tile map.
diff --git a/include/isogfx/gfx2d.h b/include/isogfx/gfx2d.h
index 59566f3..33a4591 100644
--- a/include/isogfx/gfx2d.h
+++ b/include/isogfx/gfx2d.h
@@ -37,12 +37,18 @@ typedef struct TileDesc {
37 }; 37 };
38} TileDesc; 38} TileDesc;
39 39
40typedef enum MapOrientation {
41 MapOrthogonal,
42 MapIsometric,
43} MapOrientation;
44
40typedef struct MapDesc { 45typedef struct MapDesc {
41 int tile_width; // Base tile width in pixels. 46 int tile_width; // Base tile width in pixels.
42 int tile_height; // Base tile height in pixels. 47 int tile_height; // Base tile height in pixels.
43 int world_width; // World width in tiles. 48 int world_width; // World width in tiles.
44 int world_height; // World height in tiles. 49 int world_height; // World height in tiles.
45 int num_tiles; // Number of tiles to allocate memory for. 50 int num_tiles; // Number of tiles to allocate memory for.
51 MapOrientation orientation; // Map orientation.
46} MapDesc; 52} MapDesc;
47 53
48typedef struct IsoGfxDesc { 54typedef struct IsoGfxDesc {
@@ -104,15 +110,8 @@ void gfx2d_set_sprite_animation(Gfx2d*, Sprite, int animation);
104/// Currently, this updates the sprite animations. 110/// Currently, this updates the sprite animations.
105void gfx2d_update(Gfx2d*, double t); 111void gfx2d_update(Gfx2d*, double t);
106 112
107// TODO: Do we really need to store the camera in the library? It's not used
108// for anything other than to render, so we could remove library state and
109// take a camera argument in render() instead.
110
111/// Set the camera.
112void gfx2d_set_camera(Gfx2d*, int x, int y);
113
114/// Render the world. 113/// Render the world.
115void gfx2d_render(Gfx2d*); 114void gfx2d_render(Gfx2d*, int camera_x, int camera_y);
116 115
117/// Draw/overlay a tile at position (x,y). 116/// Draw/overlay a tile at position (x,y).
118/// 117///
@@ -121,6 +120,16 @@ void gfx2d_render(Gfx2d*);
121/// position (x,y) instead, use gfx2d_set_tile(). 120/// position (x,y) instead, use gfx2d_set_tile().
122void gfx2d_draw_tile(Gfx2d*, int x, int y, Tile); 121void gfx2d_draw_tile(Gfx2d*, int x, int y, Tile);
123 122
123/// Clip camera coordinates to the loaded map.
124///
125/// This is useful for implementing camera movement, in which you typically
126/// want the camera to stay within the map's bounds.
127///
128/// (x,y) are the top-left coordinates of the camera.
129///
130/// A map must have previously been loaded.
131void gfx2d_clip_camera(const Gfx2d*, float* x, float* y);
132
124/// Get the virtual screen's dimensions. 133/// Get the virtual screen's dimensions.
125void gfx2d_get_screen_size(const Gfx2d*, int* width, int* height); 134void gfx2d_get_screen_size(const Gfx2d*, int* width, int* height);
126 135
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
23typedef struct ivec2 { 29typedef 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
82static inline ivec2 ivec2_mul(ivec2 a, ivec2 b) {
83 return (ivec2){.x = a.x * b.x, .y = a.y * b.y};
84}
85
76static inline ivec2 ivec2_scale(ivec2 a, int s) { 86static 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
86static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } 96static 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.
89static ivec2 map2screen( 99///
100/// Camera coordinates are in pixels. Map coordinates are in tiles.
101static 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
151static inline const Pixel* screen_xy_const_ref( 163static 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
161static inline Pixel screen_xy(Screen* screen, int x, int y) { 173static 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
764void gfx2d_set_camera(Gfx2d* gfx, int x, int y) { 787void 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); 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
811static 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
828void 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
790void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) { 845void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) {
791 assert(gfx); 846 assert(gfx);
792 assert(width); 847 assert(width);