summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend.c28
-rw-r--r--src/gfx2d.c583
2 files changed, 388 insertions, 223 deletions
diff --git a/src/backend.c b/src/backend.c
index 80c5974..4bb3592 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -13,7 +13,7 @@
13#include <assert.h> 13#include <assert.h>
14#include <stdlib.h> 14#include <stdlib.h>
15 15
16typedef struct IsoBackend { 16typedef struct Gfx2dBackend {
17 Gfx* gfx; 17 Gfx* gfx;
18 Mesh* quad_mesh; 18 Mesh* quad_mesh;
19 /// The screen or "iso screen" refers to the colour buffer of the iso graphics 19 /// The screen or "iso screen" refers to the colour buffer of the iso graphics
@@ -29,12 +29,12 @@ typedef struct IsoBackend {
29 int viewport_x, viewport_y, viewport_width, viewport_height; 29 int viewport_x, viewport_y, viewport_width, viewport_height;
30 double stretch; // Stretch factor from iso screen dimensions to viewport 30 double stretch; // Stretch factor from iso screen dimensions to viewport
31 // dimensions. 31 // dimensions.
32} IsoBackend; 32} Gfx2dBackend;
33 33
34IsoBackend* iso_backend_init(const IsoGfx* iso) { 34Gfx2dBackend* gfx2d_backend_init(const Gfx2d* iso) {
35 assert(iso); 35 assert(iso);
36 36
37 IsoBackend* backend = calloc(1, sizeof(IsoBackend)); 37 Gfx2dBackend* backend = calloc(1, sizeof(Gfx2dBackend));
38 if (!backend) { 38 if (!backend) {
39 return nullptr; 39 return nullptr;
40 } 40 }
@@ -45,7 +45,7 @@ IsoBackend* iso_backend_init(const IsoGfx* iso) {
45 GfxCore* gfxcore = gfx_get_core(backend->gfx); 45 GfxCore* gfxcore = gfx_get_core(backend->gfx);
46 46
47 int screen_width, screen_height; 47 int screen_width, screen_height;
48 isogfx_get_screen_size(iso, &screen_width, &screen_height); 48 gfx2d_get_screen_size(iso, &screen_width, &screen_height);
49 49
50 if (!((backend->screen_texture = gfx_make_texture( 50 if (!((backend->screen_texture = gfx_make_texture(
51 gfxcore, &(TextureDesc){.width = screen_width, 51 gfxcore, &(TextureDesc){.width = screen_width,
@@ -95,10 +95,10 @@ cleanup:
95 return nullptr; 95 return nullptr;
96} 96}
97 97
98void iso_backend_shutdown(IsoBackend** ppApp) { 98void gfx2d_backend_shutdown(Gfx2dBackend** ppApp) {
99 assert(ppApp); 99 assert(ppApp);
100 100
101 IsoBackend* app = *ppApp; 101 Gfx2dBackend* app = *ppApp;
102 if (!app) { 102 if (!app) {
103 return; 103 return;
104 } 104 }
@@ -106,8 +106,8 @@ void iso_backend_shutdown(IsoBackend** ppApp) {
106 gfx_destroy(&app->gfx); 106 gfx_destroy(&app->gfx);
107} 107}
108 108
109void iso_backend_resize_window( 109void gfx2d_backend_resize_window(
110 IsoBackend* app, const IsoGfx* iso, int width, int height) { 110 Gfx2dBackend* app, const Gfx2d* iso, int width, int height) {
111 assert(app); 111 assert(app);
112 assert(iso); 112 assert(iso);
113 113
@@ -116,7 +116,7 @@ void iso_backend_resize_window(
116 116
117 // Virtual screen dimensions. 117 // Virtual screen dimensions.
118 int screen_width, screen_height; 118 int screen_width, screen_height;
119 isogfx_get_screen_size(iso, &screen_width, &screen_height); 119 gfx2d_get_screen_size(iso, &screen_width, &screen_height);
120 120
121 // Stretch the virtual screen onto the viewport while respecting the screen's 121 // Stretch the virtual screen onto the viewport while respecting the screen's
122 // aspect ratio to prevent distortion. 122 // aspect ratio to prevent distortion.
@@ -135,11 +135,11 @@ void iso_backend_resize_window(
135 } 135 }
136} 136}
137 137
138void iso_backend_render(const IsoBackend* app, const IsoGfx* iso) { 138void gfx2d_backend_render(const Gfx2dBackend* app, const Gfx2d* iso) {
139 assert(app); 139 assert(app);
140 assert(iso); 140 assert(iso);
141 141
142 const Pixel* screen = isogfx_get_screen_buffer(iso); 142 const Pixel* screen = gfx2d_get_screen_buffer(iso);
143 assert(screen); 143 assert(screen);
144 gfx_update_texture(app->screen_texture, &(TextureDataDesc){.pixels = screen}); 144 gfx_update_texture(app->screen_texture, &(TextureDataDesc){.pixels = screen});
145 145
@@ -162,8 +162,8 @@ void iso_backend_render(const IsoBackend* app, const IsoGfx* iso) {
162 gfx_end_frame(gfxcore); 162 gfx_end_frame(gfxcore);
163} 163}
164 164
165bool iso_backend_get_mouse_position( 165bool gfx2d_backend_get_mouse_position(
166 const IsoBackend* app, double window_x, double window_y, double* x, 166 const Gfx2dBackend* app, double window_x, double window_y, double* x,
167 double* y) { 167 double* y) {
168 assert(app); 168 assert(app);
169 169
diff --git a/src/gfx2d.c b/src/gfx2d.c
index da265b0..e79dd3e 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;
@@ -32,11 +38,11 @@ typedef struct vec2 {
32// Renderer state. 38// Renderer state.
33// ----------------------------------------------------------------------------- 39// -----------------------------------------------------------------------------
34 40
35typedef struct CoordSystem { 41typedef struct IsoCoordSystem {
36 ivec2 o; // Origin. 42 ivec2 o; // Origin.
37 ivec2 x; 43 ivec2 x;
38 ivec2 y; 44 ivec2 y;
39} CoordSystem; 45} IsoCoordSystem;
40 46
41typedef struct Screen { 47typedef struct Screen {
42 int width; 48 int width;
@@ -52,9 +58,9 @@ typedef struct SpriteInstance {
52 int frame; // Current frame of animation. 58 int frame; // Current frame of animation.
53} SpriteInstance; 59} SpriteInstance;
54 60
55typedef struct IsoGfx { 61typedef struct Gfx2d {
56 Screen screen; 62 Screen screen;
57 CoordSystem iso_space; 63 IsoCoordSystem iso_space;
58 ivec2 camera; 64 ivec2 camera;
59 double last_animation_time; 65 double last_animation_time;
60 Tile next_tile; // For procedurally-generated tiles. 66 Tile next_tile; // For procedurally-generated tiles.
@@ -63,7 +69,7 @@ typedef struct IsoGfx {
63 SpriteInstance* head_sprite; // Head of sprites list. 69 SpriteInstance* head_sprite; // Head of sprites list.
64 memstack stack; 70 memstack stack;
65 size_t watermark; 71 size_t watermark;
66} IsoGfx; 72} Gfx2d;
67 73
68// ----------------------------------------------------------------------------- 74// -----------------------------------------------------------------------------
69// Math and world / tile / screen access. 75// Math and world / tile / screen access.
@@ -73,23 +79,70 @@ 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}
79 89
80static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; } 90static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; }
81 91
82static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) {
83 return (ivec2){.x = (iso.x - iso.y) * (s / 2) + (w / 2),
84 .y = (iso.x + iso.y) * (t / 2)};
85}
86
87static inline vec2 vec2_add(vec2 a, vec2 b) { 92static inline vec2 vec2_add(vec2 a, vec2 b) {
88 return (vec2){.x = a.x + b.x, .y = a.y + b.y}; 93 return (vec2){.x = a.x + b.x, .y = a.y + b.y};
89} 94}
90 95
91static 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}; }
92 97
98/// Map ortho coordinates to screen coordinates.
99///
100/// Camera coordinates are in pixels. Map coordinates are in tiles.
101static ivec2 ortho2screen(
102 ivec2 camera, int tile_width, int tile_height, int map_x, int map_y) {
103 return ivec2_add(
104 ivec2_neg(camera),
105 ivec2_mul((ivec2){map_x, map_y}, (ivec2){tile_width, tile_height}));
106}
107
108// Not actually used because we pre-compute the two axis vectors instead.
109// See make_iso_coord_system() and the other definition of iso2cart() below.
110// static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) {
111// return (ivec2){.x = (iso.x - iso.y) * (s / 2) + (w / 2),
112// .y = (iso.x + iso.y) * (t / 2)};
113// }
114
115/// Create the basis for the isometric coordinate system with origin and vectors
116/// expressed in the Cartesian system.
117static IsoCoordSystem make_iso_coord_system(
118 const Tm_Map* const map, const Screen* const screen) {
119 assert(map);
120 assert(screen);
121 const ivec2 o = {screen->width / 2, 0};
122 const ivec2 x = {
123 .x = map->base_tile_width / 2, .y = map->base_tile_height / 2};
124 const ivec2 y = {
125 .x = -map->base_tile_width / 2, .y = map->base_tile_height / 2};
126 return (IsoCoordSystem){o, x, y};
127}
128
129/// Map isometric coordinates to Cartesian coordinates.
130///
131/// For a tile, this gets the screen position of the top diamond-corner of the
132/// tile.
133///
134/// Takes the camera displacement into account.
135static ivec2 iso2cart(
136 const IsoCoordSystem iso_space, ivec2 camera, int iso_x, int iso_y) {
137 const ivec2 vx_offset = ivec2_scale(iso_space.x, iso_x);
138 const ivec2 vy_offset = ivec2_scale(iso_space.y, iso_y);
139 const ivec2 origin_world_space =
140 ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset));
141 const ivec2 origin_view_space =
142 ivec2_add(origin_world_space, ivec2_neg(camera));
143 return origin_view_space;
144}
145
93// Method 1. 146// Method 1.
94// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { 147// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
95// const double x = cart.x - (double)(w / 2); 148// const double x = cart.x - (double)(w / 2);
@@ -103,8 +156,8 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
103 const double one_over_s = 1. / (double)s; 156 const double one_over_s = 1. / (double)s;
104 const double one_over_t = 1. / (double)t; 157 const double one_over_t = 1. / (double)t;
105 const double x = cart.x - (double)(w / 2); 158 const double x = cart.x - (double)(w / 2);
106 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)),
107 .y = (-one_over_s * x + one_over_t * cart.y)}; 160 .y = ((-one_over_s * x) + (one_over_t * cart.y))};
108} 161}
109 162
110static inline const Pixel* screen_xy_const_ref( 163static inline const Pixel* screen_xy_const_ref(
@@ -114,10 +167,10 @@ static inline const Pixel* screen_xy_const_ref(
114 assert(y >= 0); 167 assert(y >= 0);
115 assert(x < screen->width); 168 assert(x < screen->width);
116 assert(y < screen->height); 169 assert(y < screen->height);
117 return &screen->pixels[y * screen->width + x]; 170 return &screen->pixels[(y * screen->width) + x];
118} 171}
119 172
120static inline Pixel screen_xy(Screen* screen, int x, int y) { 173static inline Pixel screen_xy(const Screen* screen, int x, int y) {
121 return *screen_xy_const_ref(screen, x, y); 174 return *screen_xy_const_ref(screen, x, y);
122} 175}
123 176
@@ -125,81 +178,67 @@ static inline Pixel* screen_xy_mut(Screen* screen, int x, int y) {
125 return (Pixel*)screen_xy_const_ref(screen, x, y); 178 return (Pixel*)screen_xy_const_ref(screen, x, y);
126} 179}
127 180
128/// Create the basis for the isometric coordinate system with origin and vectors
129/// expressed in the Cartesian system.
130static CoordSystem make_iso_coord_system(
131 const Tm_Map* const map, const Screen* const screen) {
132 assert(map);
133 assert(screen);
134 const ivec2 o = {screen->width / 2, 0};
135 const ivec2 x = {
136 .x = map->base_tile_width / 2, .y = map->base_tile_height / 2};
137 const ivec2 y = {
138 .x = -map->base_tile_width / 2, .y = map->base_tile_height / 2};
139 return (CoordSystem){o, x, y};
140}
141
142// ----------------------------------------------------------------------------- 181// -----------------------------------------------------------------------------
143// Renderer, world and tile management. 182// Renderer, world and tile management.
144// ----------------------------------------------------------------------------- 183// -----------------------------------------------------------------------------
145 184
146IsoGfx* isogfx_new(const IsoGfxDesc* desc) { 185Gfx2d* gfx2d_new(const Gfx2dDesc* desc) {
147 assert(desc->screen_width > 0); 186 assert(desc->screen_width > 0);
148 assert(desc->screen_height > 0); 187 assert(desc->screen_height > 0);
149 // Part of our implementation assumes even widths and heights for precision. 188 // Part of our implementation assumes even widths and heights for precision.
150 assert((desc->screen_width & 1) == 0); 189 assert((desc->screen_width & 1) == 0);
151 assert((desc->screen_height & 1) == 0); 190 assert((desc->screen_height & 1) == 0);
152 191
153 IsoGfx tmp = {0}; 192 Gfx2d tmp = {0};
154 if (!memstack_make(&tmp.stack, desc->memory_size, desc->memory)) { 193 if (!memstack_make(&tmp.stack, desc->memory_size, desc->memory)) {
155 goto cleanup; 194 goto cleanup;
156 } 195 }
157 IsoGfx* iso = 196 Gfx2d* gfx =
158 memstack_alloc_aligned(&tmp.stack, sizeof(IsoGfx), alignof(IsoGfx)); 197 memstack_alloc_aligned(&tmp.stack, sizeof(Gfx2d), alignof(Gfx2d));
159 *iso = tmp; 198 *gfx = tmp;
160 199
161 const size_t screen_size_bytes = 200 const size_t screen_size_bytes =
162 desc->screen_width * desc->screen_height * sizeof(Pixel); 201 desc->screen_width * desc->screen_height * sizeof(Pixel);
163 Pixel* screen = 202 Pixel* screen =
164 memstack_alloc_aligned(&iso->stack, screen_size_bytes, alignof(Pixel)); 203 memstack_alloc_aligned(&gfx->stack, screen_size_bytes, alignof(Pixel));
165 204
166 iso->screen = (Screen){.width = desc->screen_width, 205 gfx->screen = (Screen){.width = desc->screen_width,
167 .height = desc->screen_height, 206 .height = desc->screen_height,
168 .pixels = screen}; 207 .pixels = screen};
169 208
170 iso->last_animation_time = 0.0; 209 gfx->last_animation_time = 0.0;
171 iso->watermark = memstack_watermark(&iso->stack); 210 gfx->watermark = memstack_watermark(&gfx->stack);
172 211
173 return iso; 212 return gfx;
174 213
175cleanup: 214cleanup:
176 isogfx_del(&iso); 215 gfx2d_del(&gfx);
177 return nullptr; 216 return nullptr;
178} 217}
179 218
180void isogfx_clear(IsoGfx* iso) { 219void gfx2d_clear(Gfx2d* gfx) {
181 assert(iso); 220 assert(gfx);
182 iso->last_animation_time = 0.0; 221 gfx->last_animation_time = 0.0;
183 iso->next_tile = 0; 222 gfx->next_tile = 0;
184 iso->map = nullptr; 223 gfx->map = nullptr;
185 iso->tileset = nullptr; 224 gfx->tileset = nullptr;
186 iso->head_sprite = nullptr; 225 gfx->head_sprite = nullptr;
187 // The base of the stack contains the IsoGfx and the screen buffer. Make sure 226 // The base of the stack contains the Gfx2d and the screen buffer. Make sure
188 // we don't clear them. 227 // we don't clear them.
189 memstack_set_watermark(&iso->stack, iso->watermark); 228 memstack_set_watermark(&gfx->stack, gfx->watermark);
190} 229}
191 230
192void isogfx_del(IsoGfx** ppIso) { 231void gfx2d_del(Gfx2d** ppGfx) {
193 assert(ppIso); 232 assert(ppGfx);
194 IsoGfx* iso = *ppIso; 233 Gfx2d* gfx = *ppGfx;
195 if (iso) { 234 if (gfx) {
196 memstack_del(&iso->stack); 235 memstack_del(&gfx->stack);
197 *ppIso = nullptr; 236 *ppGfx = nullptr;
198 } 237 }
199} 238}
200 239
201void isogfx_make_map(IsoGfx* iso, const MapDesc* desc) { 240void gfx2d_make_map(Gfx2d* gfx, const MapDesc* desc) {
202 assert(iso); 241 assert(gfx);
203 assert(desc); 242 assert(desc);
204 assert(desc->tile_width > 0); 243 assert(desc->tile_width > 0);
205 assert(desc->tile_height > 0); 244 assert(desc->tile_height > 0);
@@ -214,7 +253,7 @@ void isogfx_make_map(IsoGfx* iso, const MapDesc* desc) {
214 assert(desc->num_tiles > 0); 253 assert(desc->num_tiles > 0);
215 254
216 // Handle recreation by destroying the previous world and sprites. 255 // Handle recreation by destroying the previous world and sprites.
217 isogfx_clear(iso); 256 gfx2d_clear(gfx);
218 257
219 const int world_size = desc->world_width * desc->world_height; 258 const int world_size = desc->world_width * desc->world_height;
220 const size_t map_size_bytes = sizeof(Tm_Map) + (world_size * sizeof(Tile)); 259 const size_t map_size_bytes = sizeof(Tm_Map) + (world_size * sizeof(Tile));
@@ -229,43 +268,45 @@ void isogfx_make_map(IsoGfx* iso, const MapDesc* desc) {
229 (desc->num_tiles * sizeof(Ts_Tile)) + 268 (desc->num_tiles * sizeof(Ts_Tile)) +
230 tile_data_size_bytes; 269 tile_data_size_bytes;
231 270
232 iso->map = memstack_alloc_aligned(&iso->stack, map_size_bytes, 4); 271 gfx->map = memstack_alloc_aligned(&gfx->stack, map_size_bytes, 4);
233 *iso->map = (Tm_Map){ 272 *gfx->map = (Tm_Map){
234 .world_width = desc->world_width, 273 .world_width = desc->world_width,
235 .world_height = desc->world_height, 274 .world_height = desc->world_height,
236 .base_tile_width = desc->tile_width, 275 .base_tile_width = desc->tile_width,
237 .base_tile_height = desc->tile_height, 276 .base_tile_height = desc->tile_height,
238 .num_layers = 1, 277 .num_layers = 1,
278 .flags =
279 (desc->orientation == MapOrthogonal) ? Tm_Orthogonal : Tm_Isometric,
239 }; 280 };
240 281
241 iso->tileset = memstack_alloc_aligned(&iso->stack, tileset_size_bytes, 4); 282 gfx->tileset = memstack_alloc_aligned(&gfx->stack, tileset_size_bytes, 4);
242 *iso->tileset = (Ts_TileSet){ 283 *gfx->tileset = (Ts_TileSet){
243 .num_tiles = desc->num_tiles, 284 .num_tiles = desc->num_tiles,
244 }; 285 };
245 286
246 iso->iso_space = make_iso_coord_system(iso->map, &iso->screen); 287 gfx->iso_space = make_iso_coord_system(gfx->map, &gfx->screen);
247} 288}
248 289
249bool isogfx_load_map(IsoGfx* iso, const char* filepath) { 290bool gfx2d_load_map(Gfx2d* gfx, const char* filepath) {
250 assert(iso); 291 assert(gfx);
251 assert(filepath); 292 assert(filepath);
252 293
253 bool success = false; 294 bool success = false;
254 295
255 // Handle recreation by destroying the previous world and sprites. 296 // Handle recreation by destroying the previous world and sprites.
256 isogfx_clear(iso); 297 gfx2d_clear(gfx);
257 298
258 // Load the map. 299 // Load the map.
259 printf("Load tile map: %s\n", filepath); 300 printf("Load tile map: %s\n", filepath);
260 WITH_FILE(filepath, { 301 WITH_FILE(filepath, {
261 const size_t map_size = get_file_size_f(file); 302 const size_t map_size = get_file_size_f(file);
262 iso->map = memstack_alloc_aligned(&iso->stack, map_size, 4); 303 gfx->map = memstack_alloc_aligned(&gfx->stack, map_size, 4);
263 success = read_file_f(file, iso->map); 304 success = read_file_f(file, gfx->map);
264 }); 305 });
265 if (!success) { 306 if (!success) {
266 goto cleanup; 307 goto cleanup;
267 } 308 }
268 Tm_Map* const map = iso->map; 309 Tm_Map* const map = gfx->map;
269 310
270 printf("Map orientation: %d\n", ((Tm_Flags*)&map->flags)->orientation); 311 printf("Map orientation: %d\n", ((Tm_Flags*)&map->flags)->orientation);
271 312
@@ -281,39 +322,39 @@ bool isogfx_load_map(IsoGfx* iso, const char* filepath) {
281 printf("Load tile set: %s\n", ts_path_cwd); 322 printf("Load tile set: %s\n", ts_path_cwd);
282 WITH_FILE(ts_path_cwd, { 323 WITH_FILE(ts_path_cwd, {
283 const size_t file_size = get_file_size_f(file); 324 const size_t file_size = get_file_size_f(file);
284 iso->tileset = memstack_alloc_aligned(&iso->stack, file_size, 4); 325 gfx->tileset = memstack_alloc_aligned(&gfx->stack, file_size, 4);
285 success = read_file_f(file, iso->tileset); 326 success = read_file_f(file, gfx->tileset);
286 }); 327 });
287 if (!success) { 328 if (!success) {
288 // TODO: Log errors using the log library. 329 // TODO: Log errors using the log library.
289 goto cleanup; 330 goto cleanup;
290 } 331 }
291 const Ts_TileSet* const tileset = iso->tileset; 332 const Ts_TileSet* const tileset = gfx->tileset;
292 printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd); 333 printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd);
293 334
294 // TODO: These assertions on input data should be library runtime errors. 335 // TODO: These assertions on input data should be library runtime errors.
295 assert(ts_validate_tileset(tileset)); 336 assert(ts_validate_tileset(tileset));
296 assert(tm_validate_map(map, tileset)); 337 assert(tm_validate_map(map, tileset));
297 338
298 iso->iso_space = make_iso_coord_system(iso->map, &iso->screen); 339 gfx->iso_space = make_iso_coord_system(gfx->map, &gfx->screen);
299 340
300 success = true; 341 success = true;
301 342
302cleanup: 343cleanup:
303 if (!success) { 344 if (!success) {
304 isogfx_clear(iso); 345 gfx2d_clear(gfx);
305 } 346 }
306 return success; 347 return success;
307} 348}
308 349
309int isogfx_world_width(const IsoGfx* iso) { 350int gfx2d_world_width(const Gfx2d* gfx) {
310 assert(iso); 351 assert(gfx);
311 return iso->map->world_width; 352 return gfx->map->world_width;
312} 353}
313 354
314int isogfx_world_height(const IsoGfx* iso) { 355int gfx2d_world_height(const Gfx2d* gfx) {
315 assert(iso); 356 assert(gfx);
316 return iso->map->world_height; 357 return gfx->map->world_height;
317} 358}
318 359
319static void make_tile_from_colour( 360static void make_tile_from_colour(
@@ -325,10 +366,10 @@ static void make_tile_from_colour(
325 const int height = tile->height; 366 const int height = tile->height;
326 const int r = width / height; 367 const int r = width / height;
327 for (int y = 0; y < height / 2; ++y) { 368 for (int y = 0; y < height / 2; ++y) {
328 const int mask_start = width / 2 - r * y - 1; 369 const int mask_start = (width / 2) - (r * y) - 1;
329 const int mask_end = width / 2 + r * y + 1; 370 const int mask_end = (width / 2) + (r * y) + 1;
330 for (int x = 0; x < width; ++x) { 371 for (int x = 0; x < width; ++x) {
331 const bool mask = (mask_start <= x) && (x <= mask_end); 372 const bool mask = ((mask_start <= x) && (x <= mask_end)) != 0;
332 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};
333 374
334 // Top half. 375 // Top half.
@@ -341,19 +382,19 @@ static void make_tile_from_colour(
341 } 382 }
342} 383}
343 384
344Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { 385Tile gfx2d_make_tile(Gfx2d* gfx, const TileDesc* desc) {
345 assert(iso); 386 assert(gfx);
346 assert(desc); 387 assert(desc);
347 // Client must create a world first. 388 // Client must create a world first.
348 assert(iso->map); 389 assert(gfx->map);
349 assert(iso->tileset); 390 assert(gfx->tileset);
350 // Currently, procedural tiles must match the base tile size. 391 // Currently, procedural tiles must match the base tile size.
351 assert(desc->width == iso->map->base_tile_width); 392 assert(desc->width == gfx->map->base_tile_width);
352 assert(desc->height == iso->map->base_tile_height); 393 assert(desc->height == gfx->map->base_tile_height);
353 // Cannot exceed max tiles. 394 // Cannot exceed max tiles.
354 assert(iso->next_tile < iso->tileset->num_tiles); 395 assert(gfx->next_tile < gfx->tileset->num_tiles);
355 396
356 const Tile tile = iso->next_tile++; 397 const Tile tile = gfx->next_tile++;
357 398
358 const size_t tile_size_bytes = desc->width * desc->height * sizeof(Pixel); 399 const size_t tile_size_bytes = desc->width * desc->height * sizeof(Pixel);
359 400
@@ -362,16 +403,16 @@ Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
362 assert(desc->width > 0); 403 assert(desc->width > 0);
363 assert(desc->height > 0); 404 assert(desc->height > 0);
364 405
365 Ts_Tile* const ts_tile = ts_tileset_get_tile_mut(iso->tileset, tile); 406 Ts_Tile* const ts_tile = ts_tileset_get_tile_mut(gfx->tileset, tile);
366 407
367 *ts_tile = (Ts_Tile){ 408 *ts_tile = (Ts_Tile){
368 .width = iso->map->base_tile_width, 409 .width = gfx->map->base_tile_width,
369 .height = iso->map->base_tile_height, 410 .height = gfx->map->base_tile_height,
370 .pixels = tile * tile_size_bytes, 411 .pixels = tile * tile_size_bytes,
371 }; 412 };
372 413
373 Pixel* const tile_pixels = 414 Pixel* const tile_pixels =
374 ts_tileset_get_tile_pixels_mut(iso->tileset, tile); 415 ts_tileset_get_tile_pixels_mut(gfx->tileset, tile);
375 make_tile_from_colour(desc->colour, ts_tile, tile_pixels); 416 make_tile_from_colour(desc->colour, ts_tile, tile_pixels);
376 break; 417 break;
377 } 418 }
@@ -389,31 +430,31 @@ Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
389 return tile; 430 return tile;
390} 431}
391 432
392void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { 433void gfx2d_set_tile(Gfx2d* gfx, int x, int y, Tile tile) {
393 assert(iso); 434 assert(gfx);
394 435
395 Tm_Layer* const layer = tm_map_get_layer_mut(iso->map, 0); 436 Tm_Layer* const layer = tm_map_get_layer_mut(gfx->map, 0);
396 Tile* map_tile = tm_layer_get_tile_mut(iso->map, layer, x, y); 437 Tile* map_tile = tm_layer_get_tile_mut(gfx->map, layer, x, y);
397 438
398 *map_tile = tile; 439 *map_tile = tile;
399} 440}
400 441
401void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { 442void gfx2d_set_tiles(Gfx2d* gfx, int x0, int y0, int x1, int y1, Tile tile) {
402 assert(iso); 443 assert(gfx);
403 for (int y = y0; y < y1; ++y) { 444 for (int y = y0; y < y1; ++y) {
404 for (int x = x0; x < x1; ++x) { 445 for (int x = x0; x < x1; ++x) {
405 isogfx_set_tile(iso, x, y, tile); 446 gfx2d_set_tile(gfx, x, y, tile);
406 } 447 }
407 } 448 }
408} 449}
409 450
410SpriteSheet isogfx_load_sprite_sheet(IsoGfx* iso, const char* filepath) { 451SpriteSheet gfx2d_load_sprite_sheet(Gfx2d* gfx, const char* filepath) {
411 assert(iso); 452 assert(gfx);
412 assert(filepath); 453 assert(filepath);
413 454
414 bool success = false; 455 bool success = false;
415 SpriteSheet spriteSheet = 0; 456 SpriteSheet spriteSheet = 0;
416 const size_t watermark = memstack_watermark(&iso->stack); 457 const size_t watermark = memstack_watermark(&gfx->stack);
417 458
418 // Load sprite sheet file. 459 // Load sprite sheet file.
419 printf("Load sprite sheet: %s\n", filepath); 460 printf("Load sprite sheet: %s\n", filepath);
@@ -421,7 +462,7 @@ SpriteSheet isogfx_load_sprite_sheet(IsoGfx* iso, const char* filepath) {
421 WITH_FILE(filepath, { 462 WITH_FILE(filepath, {
422 const size_t file_size = get_file_size_f(file); 463 const size_t file_size = get_file_size_f(file);
423 ss_sheet = 464 ss_sheet =
424 memstack_alloc_aligned(&iso->stack, file_size, alignof(Ss_SpriteSheet)); 465 memstack_alloc_aligned(&gfx->stack, file_size, alignof(Ss_SpriteSheet));
425 success = read_file_f(file, ss_sheet); 466 success = read_file_f(file, ss_sheet);
426 }); 467 });
427 if (!success) { 468 if (!success) {
@@ -434,63 +475,63 @@ SpriteSheet isogfx_load_sprite_sheet(IsoGfx* iso, const char* filepath) {
434cleanup: 475cleanup:
435 if (!success) { 476 if (!success) {
436 if (ss_sheet) { 477 if (ss_sheet) {
437 memstack_set_watermark(&iso->stack, watermark); 478 memstack_set_watermark(&gfx->stack, watermark);
438 } 479 }
439 } 480 }
440 return spriteSheet; 481 return spriteSheet;
441} 482}
442 483
443Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) { 484Sprite gfx2d_make_sprite(Gfx2d* gfx, SpriteSheet sheet) {
444 assert(iso); 485 assert(gfx);
445 assert(sheet); 486 assert(sheet);
446 487
447 // TODO: Remove memstack_alloc() and replace it with a same-name macro that 488 // TODO: Remove memstack_alloc() and replace it with a same-name macro that
448 // calls memstack_alloc_aligned() with sizeof/alignof. No real point in 489 // calls memstack_alloc_aligned() with sizeof/alignof. No real point in
449 // having unaligned allocations. 490 // having unaligned allocations.
450 SpriteInstance* sprite = memstack_alloc_aligned( 491 SpriteInstance* sprite = memstack_alloc_aligned(
451 &iso->stack, sizeof(SpriteInstance), alignof(SpriteInstance)); 492 &gfx->stack, sizeof(SpriteInstance), alignof(SpriteInstance));
452 493
453 sprite->sheet = (const Ss_SpriteSheet*)sheet; 494 sprite->sheet = (const Ss_SpriteSheet*)sheet;
454 sprite->next = iso->head_sprite; 495 sprite->next = gfx->head_sprite;
455 iso->head_sprite = sprite; 496 gfx->head_sprite = sprite;
456 497
457 return (Sprite)sprite; 498 return (Sprite)sprite;
458} 499}
459 500
460void isogfx_set_sprite_position(IsoGfx* iso, Sprite hSprite, int x, int y) { 501void gfx2d_set_sprite_position(Gfx2d* gfx, Sprite hSprite, int x, int y) {
461 assert(iso); 502 assert(gfx);
462 SpriteInstance* sprite = (SpriteInstance*)hSprite; 503 SpriteInstance* sprite = (SpriteInstance*)hSprite;
463 sprite->position.x = x; 504 sprite->position.x = x;
464 sprite->position.y = y; 505 sprite->position.y = y;
465} 506}
466 507
467void isogfx_set_sprite_animation(IsoGfx* iso, Sprite hSprite, int animation) { 508void gfx2d_set_sprite_animation(Gfx2d* gfx, Sprite hSprite, int animation) {
468 assert(iso); 509 assert(gfx);
469 SpriteInstance* sprite = (SpriteInstance*)hSprite; 510 SpriteInstance* sprite = (SpriteInstance*)hSprite;
470 sprite->animation = animation; 511 sprite->animation = animation;
471} 512}
472 513
473void isogfx_update(IsoGfx* iso, double t) { 514void gfx2d_update(Gfx2d* gfx, double t) {
474 assert(iso); 515 assert(gfx);
475 516
476 // If this is the first time update() is called after initialization, just 517 // If this is the first time update() is called after initialization, just
477 // record the starting animation time. 518 // record the starting animation time.
478 if (iso->last_animation_time == 0.0) { 519 if (gfx->last_animation_time == 0.0) {
479 iso->last_animation_time = t; 520 gfx->last_animation_time = t;
480 return; 521 return;
481 } 522 }
482 523
483 if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) { 524 if ((t - gfx->last_animation_time) >= ANIMATION_UPDATE_DELTA) {
484 // TODO: Consider linking animated sprites in a separate list so that we 525 // TODO: Consider linking animated sprites in a separate list so that we
485 // only walk over those here and not also the static sprites. 526 // only walk over those here and not also the static sprites.
486 for (SpriteInstance* sprite = iso->head_sprite; sprite; 527 for (SpriteInstance* sprite = gfx->head_sprite; sprite;
487 sprite = sprite->next) { 528 sprite = sprite->next) {
488 const Ss_SpriteSheet* sheet = sprite->sheet; 529 const Ss_SpriteSheet* sheet = sprite->sheet;
489 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); 530 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation);
490 sprite->frame = (sprite->frame + 1) % row->num_cols; 531 sprite->frame = (sprite->frame + 1) % row->num_cols;
491 } 532 }
492 533
493 iso->last_animation_time = t; 534 gfx->last_animation_time = t;
494 } 535 }
495} 536}
496 537
@@ -498,18 +539,6 @@ void isogfx_update(IsoGfx* iso, double t) {
498// Rendering and picking. 539// Rendering and picking.
499// ----------------------------------------------------------------------------- 540// -----------------------------------------------------------------------------
500 541
501/// Get the screen position of the top diamond-corner of the tile at world
502/// (x,y).
503static ivec2 GetTileScreenOrigin(
504 const CoordSystem iso_space, ivec2 camera, int world_x, int world_y) {
505 const ivec2 vx_offset = ivec2_scale(iso_space.x, world_x);
506 const ivec2 vy_offset = ivec2_scale(iso_space.y, world_y);
507 const ivec2 screen_origin =
508 ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset));
509 const ivec2 origin_view_space = ivec2_add(screen_origin, ivec2_neg(camera));
510 return origin_view_space;
511}
512
513static Pixel alpha_blend(Pixel src, Pixel dst) { 542static Pixel alpha_blend(Pixel src, Pixel dst) {
514 if ((src.a == 255) || (dst.a == 0)) { 543 if ((src.a == 255) || (dst.a == 0)) {
515 return src; 544 return src;
@@ -541,13 +570,13 @@ static void draw_rect(
541 Screen* screen, ivec2 top_left, int rect_width, int rect_height, 570 Screen* screen, ivec2 top_left, int rect_width, int rect_height,
542 const Pixel* pixels, const uint8_t* indices) { 571 const Pixel* pixels, const uint8_t* indices) {
543 assert(screen); 572 assert(screen);
573 assert(pixels);
544 574
545#define rect_pixel(X, Y) \ 575#define rect_pixel(X, Y) \
546 (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X]) 576 (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X])
547 577
548 // 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
549 // draw only the visible portion. 579 // draw only the visible portion.
550#define max(a, b) (a > b ? a : b)
551 const int px_offset = max(0, -top_left.x); 580 const int px_offset = max(0, -top_left.x);
552 const int py_offset = max(0, -top_left.y); 581 const int py_offset = max(0, -top_left.y);
553 582
@@ -568,137 +597,273 @@ static void draw_rect(
568 } 597 }
569} 598}
570 599
571/// Draw a tile. 600/// Draw a tile in an orthogonal map.
572/// 601static void draw_tile_ortho(Gfx2d* gfx, Tile tile, int x, int y) {
573/// 'screen_origin' is the screen coordinates of the top diamond-corner of the 602 assert(gfx);
574/// tile (the base tile for super tiles). 603 assert(gfx->tileset);
575/// World (0, 0) -> (screen_width / 2, 0). 604 assert(x >= 0);
576static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) { 605 assert(y >= 0);
577 assert(iso); 606 assert(x < gfx->map->world_width);
578 assert(iso->tileset); 607 assert(y < gfx->map->world_height);
579 608
580 const Ts_Tile* pTile = ts_tileset_get_tile(iso->tileset, tile); 609 const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile);
581 const Pixel* pixels = ts_tileset_get_tile_pixels(iso->tileset, tile); 610 const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile);
611
612 const ivec2 screen_origin = ortho2screen(
613 gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, x, y);
614
615 draw_rect(
616 &gfx->screen, screen_origin, pTile->width, pTile->height, pixels,
617 nullptr);
618}
619
620/// Draw a tile in an isometric map.
621static void draw_tile_iso(Gfx2d* gfx, Tile tile, int iso_x, int iso_y) {
622 assert(gfx);
623 assert(gfx->tileset);
624 assert(iso_x >= 0);
625 assert(iso_y >= 0);
626 assert(iso_x < gfx->map->world_width);
627 assert(iso_y < gfx->map->world_height);
628
629 const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile);
630 const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile);
631
632 // Compute the screen coordinates of the top diamond-corner of the tile (the
633 // base tile for super tiles).
634 // World (0, 0) -> (screen_width / 2, 0).
635 const ivec2 screen_origin =
636 iso2cart(gfx->iso_space, gfx->camera, iso_x, iso_y);
582 637
583 // Move from the top diamond-corner to the top-left corner of the tile image. 638 // Move from the top diamond-corner to the top-left corner of the tile image.
584 // For regular tiles, tile height == base tile height, so the y offset is 0. 639 // For regular tiles, tile height == base tile height, so the y offset is 0.
585 // For super tiles, move as high up as the height of the tile. 640 // For super tiles, move as high up as the height of the tile.
586 const ivec2 offset = { 641 const ivec2 offset = {
587 -(iso->map->base_tile_width / 2), 642 -(gfx->map->base_tile_width / 2),
588 pTile->height - iso->map->base_tile_height}; 643 pTile->height - gfx->map->base_tile_height};
589 const ivec2 top_left = ivec2_add(screen_origin, offset); 644 const ivec2 top_left = ivec2_add(screen_origin, offset);
590 645
591 draw_rect( 646 draw_rect(
592 &iso->screen, top_left, pTile->width, pTile->height, pixels, nullptr); 647 &gfx->screen, top_left, pTile->width, pTile->height, pixels, nullptr);
593} 648}
594 649
595static void draw_map(IsoGfx* iso) { 650static void draw_map_ortho(Gfx2d* gfx) {
596 assert(iso); 651 assert(gfx);
652 assert(gfx->map);
597 653
598 const int W = iso->screen.width; 654 // TODO: Handle multiple layers.
599 const int H = iso->screen.height; 655 const Tm_Layer* layer = tm_map_get_layer(gfx->map, 0);
600 656
601 memset(iso->screen.pixels, 0, W * H * sizeof(Pixel)); 657 // TODO: This currently renders with tile granularity. Do so instead in terms
658 // of pixels for more accurate camera panning. The camera coordinates are
659 // already given in pixels.
660 for (int wy = gfx->camera.y / gfx->map->base_tile_height;
661 wy < gfx->map->world_height; ++wy) {
662 for (int wx = gfx->camera.x / gfx->map->base_tile_width;
663 wx < gfx->map->world_width; ++wx) {
664 const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy);
665 draw_tile_ortho(gfx, tile, wx, wy);
666 }
667 }
668}
669
670static void draw_map_iso(Gfx2d* gfx) {
671 assert(gfx);
672 assert(gfx->map);
602 673
603 const Tm_Layer* layer = tm_map_get_layer(iso->map, 0); 674 // TODO: Support for multiple layers.
675 const Tm_Layer* layer = tm_map_get_layer(gfx->map, 0);
604 676
605 // TODO: Culling. 677 // TODO: Culling.
606 // Ex: map the screen corners to tile space to cull. 678 // Ex: map the screen corners to tile space to cull.
607 // Ex: walk in screen space and fetch the tile. 679 // Ex: walk in screen space and fetch the tile.
608 // The tile-centric approach might be more cache-friendly since the 680 // The tile-centric approach might be more cache-friendly since the
609 // screen-centric approach would juggle multiple tiles throughout the scan. 681 // screen-centric approach would juggle multiple tiles throughout the scan.
610 for (int wy = 0; wy < iso->map->world_height; ++wy) { 682 for (int wy = 0; wy < gfx->map->world_height; ++wy) {
611 for (int wx = 0; wx < iso->map->world_width; ++wx) { 683 for (int wx = 0; wx < gfx->map->world_width; ++wx) {
612 const Tile tile = tm_layer_get_tile(iso->map, layer, wx, wy); 684 const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy);
613 const ivec2 screen_origin = 685 draw_tile_iso(gfx, tile, wx, wy);
614 GetTileScreenOrigin(iso->iso_space, iso->camera, wx, wy);
615 draw_tile(iso, screen_origin, tile);
616 } 686 }
617 } 687 }
618} 688}
619 689
620static void draw_sprite( 690static void draw_map(Gfx2d* gfx) {
621 IsoGfx* iso, ivec2 origin, const SpriteInstance* sprite, 691 assert(gfx);
622 const Ss_SpriteSheet* sheet) { 692 assert(gfx->map);
623 assert(iso); 693 assert(gfx->screen.pixels);
694
695 const int W = gfx->screen.width;
696 const int H = gfx->screen.height;
697
698 memset(gfx->screen.pixels, 0, W * H * sizeof(Pixel));
699
700 const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags;
701 switch (flags->orientation) {
702 case Tm_Orthogonal:
703 draw_map_ortho(gfx);
704 break;
705 case Tm_Isometric:
706 draw_map_iso(gfx);
707 break;
708 }
709}
710
711/// Draw a sprite in an orthogonal/Cartesian coordinate system.
712static void draw_sprite_ortho(
713 Gfx2d* gfx, const SpriteInstance* sprite, const Ss_SpriteSheet* sheet) {
714 assert(gfx);
624 assert(sprite); 715 assert(sprite);
625 assert(sheet); 716 assert(sheet);
626 assert(sprite->animation >= 0); 717 assert(sprite->animation >= 0);
627 assert(sprite->animation < sheet->num_rows); 718 assert(sprite->animation < sheet->num_rows);
628 assert(sprite->frame >= 0); 719 assert(sprite->frame >= 0);
629 720
721 // Apply an offset similarly to how we offset tiles. The sprite is offset by
722 // -base_tile_width/2 along the x-axis to align the sprite with the leftmost
723 // edge of the tile it is on.
724 const ivec2 screen_origin = ortho2screen(
725 gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height,
726 sprite->position.x, sprite->position.y);
727
630 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); 728 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation);
631 const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame); 729 const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame);
632 draw_rect( 730 draw_rect(
633 &iso->screen, origin, sheet->sprite_width, sheet->sprite_height, 731 &gfx->screen, screen_origin, sheet->sprite_width, sheet->sprite_height,
634 sheet->palette.colours, frame); 732 sheet->palette.colours, frame);
635} 733}
636 734
637static void draw_sprites(IsoGfx* iso) { 735/// Draw a sprite in an isometric coordinate system.
638 assert(iso); 736static void draw_sprite_iso(
737 Gfx2d* gfx, const SpriteInstance* sprite, const Ss_SpriteSheet* sheet) {
738 assert(gfx);
739 assert(sprite);
740 assert(sheet);
741 assert(sprite->animation >= 0);
742 assert(sprite->animation < sheet->num_rows);
743 assert(sprite->frame >= 0);
744
745 // Apply an offset similarly to how we offset tiles. The sprite is offset by
746 // -base_tile_width/2 along the x-axis to align the sprite with the leftmost
747 // edge of the tile it is on.
748 const ivec2 screen_origin = iso2cart(
749 gfx->iso_space, gfx->camera, sprite->position.x, sprite->position.y);
750 const ivec2 offset = {-(gfx->map->base_tile_width / 2), 0};
751 const ivec2 top_left = ivec2_add(screen_origin, offset);
752
753 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation);
754 const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame);
755 draw_rect(
756 &gfx->screen, top_left, sheet->sprite_width, sheet->sprite_height,
757 sheet->palette.colours, frame);
758}
639 759
640 for (const SpriteInstance* sprite = iso->head_sprite; sprite; 760static void draw_sprites(Gfx2d* gfx) {
641 sprite = sprite->next) { 761 assert(gfx);
642 const Ss_SpriteSheet* sheet = sprite->sheet; 762 assert(gfx->map);
643 assert(sheet);
644 763
645 const ivec2 screen_origin = GetTileScreenOrigin( 764 const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags;
646 iso->iso_space, iso->camera, sprite->position.x, sprite->position.y); 765 switch (flags->orientation) {
647 draw_sprite(iso, screen_origin, sprite, sheet); 766 case Tm_Orthogonal:
767 for (const SpriteInstance* sprite = gfx->head_sprite; sprite;
768 sprite = sprite->next) {
769 draw_sprite_ortho(gfx, sprite, sprite->sheet);
770 }
771 break;
772 case Tm_Isometric:
773 for (const SpriteInstance* sprite = gfx->head_sprite; sprite;
774 sprite = sprite->next) {
775 draw_sprite_iso(gfx, sprite, sprite->sheet);
776 }
777 break;
648 } 778 }
649} 779}
650 780
651void isogfx_set_camera(IsoGfx* iso, int x, int y) { 781void gfx2d_render(Gfx2d* gfx, int camera_x, int camera_y) {
652 assert(iso); 782 assert(gfx);
653 iso->camera = (ivec2){x, y}; 783 // Storing the camera mostly for debugging convenience. It could otherwise be
784 // passed around.
785 gfx->camera = (ivec2){camera_x, camera_y};
786 draw_map(gfx);
787 draw_sprites(gfx);
654} 788}
655 789
656void isogfx_render(IsoGfx* iso) { 790void gfx2d_draw_tile(Gfx2d* gfx, int x, int y, Tile tile) {
657 assert(iso); 791 assert(gfx);
658 draw_map(iso); 792 assert(gfx->map);
659 draw_sprites(iso); 793
794 const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags;
795 switch (flags->orientation) {
796 case Tm_Orthogonal:
797 draw_tile_ortho(gfx, tile, x, y);
798 break;
799 case Tm_Isometric:
800 draw_tile_iso(gfx, tile, x, y);
801 break;
802 }
660} 803}
661 804
662void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) { 805static void clip_camera_ortho(const Gfx2d* gfx, float* x, float* y) {
663 assert(iso); 806 assert(gfx);
664 assert(x >= 0); 807 assert(gfx->map);
665 assert(y >= 0);
666 assert(x < iso->map->world_width);
667 assert(y < iso->map->world_height);
668 808
669 const ivec2 screen_origin = 809 if (x != nullptr) {
670 GetTileScreenOrigin(iso->iso_space, iso->camera, x, y); 810 const int width_pixels = gfx->map->world_width * gfx->map->base_tile_width;
671 draw_tile(iso, screen_origin, tile); 811 const int x_max = width_pixels - gfx->screen.width;
812 *x = min((float)x_max, max(0.F, *x));
813 }
814 if (y != nullptr) {
815 const int height_pixels =
816 gfx->map->world_height * gfx->map->base_tile_height;
817 const int y_max = height_pixels - gfx->screen.height;
818 *y = min((float)y_max, max(0.F, *y));
819 }
820}
821
822void gfx2d_clip_camera(const Gfx2d* gfx, float* x, float* y) {
823 assert(gfx);
824 assert(gfx->map);
825 assert(x);
826 assert(y);
827
828 const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags;
829 switch (flags->orientation) {
830 case Tm_Orthogonal:
831 clip_camera_ortho(gfx, x, y);
832 break;
833 case Tm_Isometric:
834 // TODO: Clip camera in isometric maps.
835 break;
836 }
672} 837}
673 838
674void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) { 839void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) {
675 assert(iso); 840 assert(gfx);
676 assert(width); 841 assert(width);
677 assert(height); 842 assert(height);
678 *width = iso->screen.width; 843 *width = gfx->screen.width;
679 *height = iso->screen.height; 844 *height = gfx->screen.height;
680} 845}
681 846
682const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) { 847const Pixel* gfx2d_get_screen_buffer(const Gfx2d* gfx) {
683 assert(iso); 848 assert(gfx);
684 return iso->screen.pixels; 849 return gfx->screen.pixels;
685} 850}
686 851
687void isogfx_pick_tile( 852void gfx2d_pick_tile(
688 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { 853 const Gfx2d* gfx, double xcart, double ycart, int* xiso, int* yiso) {
689 assert(iso); 854 assert(gfx);
690 assert(xiso); 855 assert(xiso);
691 assert(yiso); 856 assert(yiso);
692 857
693 const vec2 camera = ivec2_to_vec2(iso->camera); 858 const vec2 camera = ivec2_to_vec2(gfx->camera);
694 const vec2 xy_cart = vec2_add(camera, (vec2){xcart, ycart}); 859 const vec2 xy_cart = vec2_add(camera, (vec2){xcart, ycart});
695 860
696 const vec2 xy_iso = cart2iso( 861 const vec2 xy_iso = cart2iso(
697 xy_cart, iso->map->base_tile_width, iso->map->base_tile_height, 862 xy_cart, gfx->map->base_tile_width, gfx->map->base_tile_height,
698 iso->screen.width); 863 gfx->screen.width);
699 864
700 if ((0 <= xy_iso.x) && (xy_iso.x < iso->map->world_width) && 865 if ((0 <= xy_iso.x) && (xy_iso.x < gfx->map->world_width) &&
701 (0 <= xy_iso.y) && (xy_iso.y < iso->map->world_height)) { 866 (0 <= xy_iso.y) && (xy_iso.y < gfx->map->world_height)) {
702 *xiso = (int)xy_iso.x; 867 *xiso = (int)xy_iso.x;
703 *yiso = (int)xy_iso.y; 868 *yiso = (int)xy_iso.y;
704 } else { 869 } else {