summaryrefslogtreecommitdiff
path: root/src/isogfx.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/isogfx.c')
-rw-r--r--src/isogfx.c706
1 files changed, 0 insertions, 706 deletions
diff --git a/src/isogfx.c b/src/isogfx.c
deleted file mode 100644
index c3a87bf..0000000
--- a/src/isogfx.c
+++ /dev/null
@@ -1,706 +0,0 @@
1#include <isogfx/isogfx.h>
2
3#include <isogfx/asset.h>
4
5#include <filesystem.h>
6#include <memstack.h>
7#include <path.h>
8
9#include <assert.h>
10#include <stdint.h>
11#include <stdio.h>
12#include <string.h>
13
14/// Maximum path length.
15#define MAX_PATH 256
16
17/// Default animation speed.
18#define ANIMATION_FPS 10
19
20/// Time between animation updates.
21#define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS)
22
23typedef struct ivec2 {
24 int x, y;
25} ivec2;
26
27typedef struct vec2 {
28 double x, y;
29} vec2;
30
31// -----------------------------------------------------------------------------
32// Renderer state.
33// -----------------------------------------------------------------------------
34
35typedef struct CoordSystem {
36 ivec2 o; // Origin.
37 ivec2 x;
38 ivec2 y;
39} CoordSystem;
40
41typedef struct Screen {
42 int width;
43 int height;
44 Pixel* pixels;
45} Screen;
46
47typedef struct SpriteInstance {
48 struct SpriteInstance* next;
49 const Ss_SpriteSheet* sheet;
50 ivec2 position;
51 int animation; // Current animation.
52 int frame; // Current frame of animation.
53} SpriteInstance;
54
55typedef struct IsoGfx {
56 Screen screen;
57 CoordSystem iso_space;
58 ivec2 camera;
59 double last_animation_time;
60 Tile next_tile; // For procedurally-generated tiles.
61 Tm_Map* map;
62 Ts_TileSet* tileset;
63 SpriteInstance* head_sprite; // Head of sprites list.
64 memstack stack;
65 size_t watermark;
66} IsoGfx;
67
68// -----------------------------------------------------------------------------
69// Math and world / tile / screen access.
70// -----------------------------------------------------------------------------
71
72static inline ivec2 ivec2_add(ivec2 a, ivec2 b) {
73 return (ivec2){.x = a.x + b.x, .y = a.y + b.y};
74}
75
76static inline ivec2 ivec2_scale(ivec2 a, int s) {
77 return (ivec2){.x = a.x * s, .y = a.y * s};
78}
79
80static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; }
81
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) {
88 return (vec2){.x = a.x + b.x, .y = a.y + b.y};
89}
90
91static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; }
92
93// Method 1.
94// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
95// const double x = cart.x - (double)(w / 2);
96// const double xiso = (x * t + cart.y * s) / (double)(s * t);
97// return (vec2){
98// .x = (int)(xiso), .y = (int)((2.0 / (double)t) * cart.y - xiso)};
99//}
100
101// Method 2.
102static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
103 const double one_over_s = 1. / (double)s;
104 const double one_over_t = 1. / (double)t;
105 const double x = cart.x - (double)(w / 2);
106 return (vec2){.x = (one_over_s * x + one_over_t * cart.y),
107 .y = (-one_over_s * x + one_over_t * cart.y)};
108}
109
110static inline const Pixel* screen_xy_const_ref(
111 const Screen* screen, int x, int y) {
112 assert(screen);
113 assert(x >= 0);
114 assert(y >= 0);
115 assert(x < screen->width);
116 assert(y < screen->height);
117 return &screen->pixels[y * screen->width + x];
118}
119
120static inline Pixel screen_xy(Screen* screen, int x, int y) {
121 return *screen_xy_const_ref(screen, x, y);
122}
123
124static inline Pixel* screen_xy_mut(Screen* screen, int x, int y) {
125 return (Pixel*)screen_xy_const_ref(screen, x, y);
126}
127
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// -----------------------------------------------------------------------------
143// Renderer, world and tile management.
144// -----------------------------------------------------------------------------
145
146IsoGfx* isogfx_new(const IsoGfxDesc* desc) {
147 assert(desc->screen_width > 0);
148 assert(desc->screen_height > 0);
149 // Part of our implementation assumes even widths and heights for precision.
150 assert((desc->screen_width & 1) == 0);
151 assert((desc->screen_height & 1) == 0);
152
153 IsoGfx tmp = {0};
154 if (!memstack_make(&tmp.stack, desc->memory_size, desc->memory)) {
155 goto cleanup;
156 }
157 IsoGfx* iso =
158 memstack_alloc_aligned(&tmp.stack, sizeof(IsoGfx), alignof(IsoGfx));
159 *iso = tmp;
160
161 const size_t screen_size_bytes =
162 desc->screen_width * desc->screen_height * sizeof(Pixel);
163 Pixel* screen =
164 memstack_alloc_aligned(&iso->stack, screen_size_bytes, alignof(Pixel));
165
166 iso->screen = (Screen){.width = desc->screen_width,
167 .height = desc->screen_height,
168 .pixels = screen};
169
170 iso->last_animation_time = 0.0;
171 iso->watermark = memstack_watermark(&iso->stack);
172
173 return iso;
174
175cleanup:
176 isogfx_del(&iso);
177 return nullptr;
178}
179
180void isogfx_clear(IsoGfx* iso) {
181 assert(iso);
182 iso->last_animation_time = 0.0;
183 iso->next_tile = 0;
184 iso->map = nullptr;
185 iso->tileset = nullptr;
186 iso->head_sprite = nullptr;
187 // The base of the stack contains the IsoGfx and the screen buffer. Make sure
188 // we don't clear them.
189 memstack_set_watermark(&iso->stack, iso->watermark);
190}
191
192void isogfx_del(IsoGfx** ppIso) {
193 assert(ppIso);
194 IsoGfx* iso = *ppIso;
195 if (iso) {
196 memstack_del(&iso->stack);
197 *ppIso = nullptr;
198 }
199}
200
201void isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) {
202 assert(iso);
203 assert(desc);
204 assert(desc->tile_width > 0);
205 assert(desc->tile_height > 0);
206 // Part of our implementation assumes even widths and heights for greater
207 // precision.
208 assert((desc->tile_width & 1) == 0);
209 assert((desc->tile_height & 1) == 0);
210 // World must be non-empty.
211 assert(desc->world_width > 0);
212 assert(desc->world_height > 0);
213 // Must have >0 tiles.
214 assert(desc->num_tiles > 0);
215
216 // Handle recreation by destroying the previous world and sprites.
217 isogfx_clear(iso);
218
219 const int world_size = desc->world_width * desc->world_height;
220 const size_t map_size_bytes = sizeof(Tm_Map) + (world_size * sizeof(Tile));
221
222 // This implies that all tiles are of the base tile dimensions.
223 // We could enhance the API to allow for supertiles as well. Take in max tile
224 // width and height and allocate enough space using those values.
225 const size_t tile_size = desc->tile_width * desc->tile_height;
226 const size_t tile_size_bytes = tile_size * sizeof(Pixel);
227 const size_t tile_data_size_bytes = desc->num_tiles * tile_size_bytes;
228 const size_t tileset_size_bytes = sizeof(Ts_TileSet) +
229 (desc->num_tiles * sizeof(Ts_Tile)) +
230 tile_data_size_bytes;
231
232 iso->map = memstack_alloc_aligned(&iso->stack, map_size_bytes, 4);
233 *iso->map = (Tm_Map){
234 .world_width = desc->world_width,
235 .world_height = desc->world_height,
236 .base_tile_width = desc->tile_width,
237 .base_tile_height = desc->tile_height,
238 .num_layers = 1,
239 };
240
241 iso->tileset = memstack_alloc_aligned(&iso->stack, tileset_size_bytes, 4);
242 *iso->tileset = (Ts_TileSet){
243 .num_tiles = desc->num_tiles,
244 };
245
246 iso->iso_space = make_iso_coord_system(iso->map, &iso->screen);
247}
248
249bool isogfx_load_world(IsoGfx* iso, const char* filepath) {
250 assert(iso);
251 assert(filepath);
252
253 bool success = false;
254
255 // Handle recreation by destroying the previous world and sprites.
256 isogfx_clear(iso);
257
258 // Load the map.
259 printf("Load tile map: %s\n", filepath);
260 WITH_FILE(filepath, {
261 const size_t map_size = get_file_size_f(file);
262 iso->map = memstack_alloc_aligned(&iso->stack, map_size, 4);
263 success = read_file_f(file, iso->map);
264 });
265 if (!success) {
266 goto cleanup;
267 }
268 Tm_Map* const map = iso->map;
269
270 // Load the tile set.
271 //
272 // Tile set path is relative to the tile map file. Make it relative to the
273 // current working directory before loading.
274 const char* ts_path = map->tileset_path;
275 char ts_path_cwd[MAX_PATH] = {0};
276 if (!path_make_relative(filepath, ts_path, ts_path_cwd, MAX_PATH)) {
277 goto cleanup;
278 }
279 printf("Load tile set: %s\n", ts_path_cwd);
280 WITH_FILE(ts_path_cwd, {
281 const size_t file_size = get_file_size_f(file);
282 iso->tileset = memstack_alloc_aligned(&iso->stack, file_size, 4);
283 success = read_file_f(file, iso->tileset);
284 });
285 if (!success) {
286 // TODO: Log errors using the log library.
287 goto cleanup;
288 }
289 const Ts_TileSet* const tileset = iso->tileset;
290 printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd);
291
292 // TODO: These assertions on input data should be library runtime errors.
293 assert(ts_validate_tileset(tileset));
294 assert(tm_validate_map(map, tileset));
295
296 iso->iso_space = make_iso_coord_system(iso->map, &iso->screen);
297
298 success = true;
299
300cleanup:
301 if (!success) {
302 isogfx_clear(iso);
303 }
304 return success;
305}
306
307int isogfx_world_width(const IsoGfx* iso) {
308 assert(iso);
309 return iso->map->world_width;
310}
311
312int isogfx_world_height(const IsoGfx* iso) {
313 assert(iso);
314 return iso->map->world_height;
315}
316
317static void make_tile_from_colour(
318 Pixel colour, const Ts_Tile* tile, Pixel* tile_pixels) {
319 assert(tile);
320 assert(tile_pixels);
321
322 const int width = tile->width;
323 const int height = tile->height;
324 const int r = width / height;
325 for (int y = 0; y < height / 2; ++y) {
326 const int mask_start = width / 2 - r * y - 1;
327 const int mask_end = width / 2 + r * y + 1;
328 for (int x = 0; x < width; ++x) {
329 const bool mask = (mask_start <= x) && (x <= mask_end);
330 const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0};
331
332 // Top half.
333 *ts_tile_xy_mut(tile_pixels, tile, x, y) = val;
334
335 // Bottom half reflects the top half.
336 const int y_reflected = height - y - 1;
337 *ts_tile_xy_mut(tile_pixels, tile, x, y_reflected) = val;
338 }
339 }
340}
341
342Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
343 assert(iso);
344 assert(desc);
345 // Client must create a world first.
346 assert(iso->map);
347 assert(iso->tileset);
348 // Currently, procedural tiles must match the base tile size.
349 assert(desc->width == iso->map->base_tile_width);
350 assert(desc->height == iso->map->base_tile_height);
351 // Cannot exceed max tiles.
352 assert(iso->next_tile < iso->tileset->num_tiles);
353
354 const Tile tile = iso->next_tile++;
355
356 const size_t tile_size_bytes = desc->width * desc->height * sizeof(Pixel);
357
358 switch (desc->type) {
359 case TileFromColour: {
360 assert(desc->width > 0);
361 assert(desc->height > 0);
362
363 Ts_Tile* const ts_tile = ts_tileset_get_tile_mut(iso->tileset, tile);
364
365 *ts_tile = (Ts_Tile){
366 .width = iso->map->base_tile_width,
367 .height = iso->map->base_tile_height,
368 .pixels = tile * tile_size_bytes,
369 };
370
371 Pixel* const tile_pixels =
372 ts_tileset_get_tile_pixels_mut(iso->tileset, tile);
373 make_tile_from_colour(desc->colour, ts_tile, tile_pixels);
374 break;
375 }
376 case TileFromFile:
377 assert(false); // TODO
378 break;
379 case TileFromMemory: {
380 assert(desc->width > 0);
381 assert(desc->height > 0);
382 assert(false); // TODO
383 break;
384 }
385 }
386
387 return tile;
388}
389
390void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) {
391 assert(iso);
392
393 Tm_Layer* const layer = tm_map_get_layer_mut(iso->map, 0);
394 Tile* map_tile = tm_layer_get_tile_mut(iso->map, layer, x, y);
395
396 *map_tile = tile;
397}
398
399void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) {
400 assert(iso);
401 for (int y = y0; y < y1; ++y) {
402 for (int x = x0; x < x1; ++x) {
403 isogfx_set_tile(iso, x, y, tile);
404 }
405 }
406}
407
408SpriteSheet isogfx_load_sprite_sheet(IsoGfx* iso, const char* filepath) {
409 assert(iso);
410 assert(filepath);
411
412 bool success = false;
413 SpriteSheet spriteSheet = 0;
414 const size_t watermark = memstack_watermark(&iso->stack);
415
416 // Load sprite sheet file.
417 printf("Load sprite sheet: %s\n", filepath);
418 Ss_SpriteSheet* ss_sheet = nullptr;
419 WITH_FILE(filepath, {
420 const size_t file_size = get_file_size_f(file);
421 ss_sheet =
422 memstack_alloc_aligned(&iso->stack, file_size, alignof(Ss_SpriteSheet));
423 success = read_file_f(file, ss_sheet);
424 });
425 if (!success) {
426 goto cleanup;
427 }
428 assert(ss_sheet);
429
430 spriteSheet = (SpriteSheet)ss_sheet;
431
432cleanup:
433 if (!success) {
434 if (ss_sheet) {
435 memstack_set_watermark(&iso->stack, watermark);
436 }
437 }
438 return spriteSheet;
439}
440
441Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) {
442 assert(iso);
443 assert(sheet);
444
445 // TODO: Remove memstack_alloc() and replace it with a same-name macro that
446 // calls memstack_alloc_aligned() with sizeof/alignof. No real point in
447 // having unaligned allocations.
448 SpriteInstance* sprite = memstack_alloc_aligned(
449 &iso->stack, sizeof(SpriteInstance), alignof(SpriteInstance));
450
451 sprite->sheet = (const Ss_SpriteSheet*)sheet;
452 sprite->next = iso->head_sprite;
453 iso->head_sprite = sprite;
454
455 return (Sprite)sprite;
456}
457
458void isogfx_set_sprite_position(IsoGfx* iso, Sprite hSprite, int x, int y) {
459 assert(iso);
460 SpriteInstance* sprite = (SpriteInstance*)hSprite;
461 sprite->position.x = x;
462 sprite->position.y = y;
463}
464
465void isogfx_set_sprite_animation(IsoGfx* iso, Sprite hSprite, int animation) {
466 assert(iso);
467 SpriteInstance* sprite = (SpriteInstance*)hSprite;
468 sprite->animation = animation;
469}
470
471void isogfx_update(IsoGfx* iso, double t) {
472 assert(iso);
473
474 // If this is the first time update() is called after initialization, just
475 // record the starting animation time.
476 if (iso->last_animation_time == 0.0) {
477 iso->last_animation_time = t;
478 return;
479 }
480
481 if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) {
482 // TODO: Consider linking animated sprites in a separate list so that we
483 // only walk over those here and not also the static sprites.
484 for (SpriteInstance* sprite = iso->head_sprite; sprite;
485 sprite = sprite->next) {
486 const Ss_SpriteSheet* sheet = sprite->sheet;
487 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation);
488 sprite->frame = (sprite->frame + 1) % row->num_cols;
489 }
490
491 iso->last_animation_time = t;
492 }
493}
494
495// -----------------------------------------------------------------------------
496// Rendering and picking.
497// -----------------------------------------------------------------------------
498
499/// Get the screen position of the top diamond-corner of the tile at world
500/// (x,y).
501static ivec2 GetTileScreenOrigin(
502 const CoordSystem iso_space, ivec2 camera, int world_x, int world_y) {
503 const ivec2 vx_offset = ivec2_scale(iso_space.x, world_x);
504 const ivec2 vy_offset = ivec2_scale(iso_space.y, world_y);
505 const ivec2 screen_origin =
506 ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset));
507 const ivec2 origin_view_space = ivec2_add(screen_origin, ivec2_neg(camera));
508 return origin_view_space;
509}
510
511static Pixel alpha_blend(Pixel src, Pixel dst) {
512 if ((src.a == 255) || (dst.a == 0)) {
513 return src;
514 }
515 const uint16_t one_minus_alpha = 255 - src.a;
516#define blend(s, d) \
517 (Channel)( \
518 (double)((uint16_t)s * (uint16_t)src.a + \
519 (uint16_t)d * one_minus_alpha) / \
520 255.0)
521 return (Pixel){.r = blend(src.r, dst.r),
522 .g = blend(src.g, dst.g),
523 .b = blend(src.b, dst.b),
524 .a = src.a};
525}
526
527/// Draw a rectangle (tile or sprite).
528///
529/// The rectangle's top-left corner is mapped to the screen space position given
530/// by 'top_left'.
531///
532/// The rectangle's pixels are assumed to be arranged in a linear, row-major
533/// fashion.
534///
535/// If indices are given, then the image is assumed to be colour-paletted, where
536/// 'pixels' is the palette and 'indices' the pixel indices. Otherwise, the
537/// image is assumed to be in plain RGBA format.
538static void draw_rect(
539 Screen* screen, ivec2 top_left, int rect_width, int rect_height,
540 const Pixel* pixels, const uint8_t* indices) {
541 assert(screen);
542
543#define rect_pixel(X, Y) \
544 (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X])
545
546 // Rect origin can be outside screen bounds, so we must offset accordingly to
547 // draw only the visible portion.
548#define max(a, b) (a > b ? a : b)
549 const int px_offset = max(0, -top_left.x);
550 const int py_offset = max(0, -top_left.y);
551
552 // Rect can exceed screen bounds, so clip along Y and X as we draw.
553 for (int py = py_offset;
554 (py < rect_height) && (top_left.y + py < screen->height); ++py) {
555 const int sy = top_left.y + py;
556 for (int px = px_offset;
557 (px < rect_width) && (top_left.x + px < screen->width); ++px) {
558 const Pixel colour = rect_pixel(px, py);
559 if (colour.a > 0) {
560 const int sx = top_left.x + px;
561 const Pixel dst = screen_xy(screen, sx, sy);
562 const Pixel final = alpha_blend(colour, dst);
563 *screen_xy_mut(screen, sx, sy) = final;
564 }
565 }
566 }
567}
568
569/// Draw a tile.
570///
571/// 'screen_origin' is the screen coordinates of the top diamond-corner of the
572/// tile (the base tile for super tiles).
573/// World (0, 0) -> (screen_width / 2, 0).
574static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) {
575 assert(iso);
576 assert(iso->tileset);
577
578 const Ts_Tile* pTile = ts_tileset_get_tile(iso->tileset, tile);
579 const Pixel* pixels = ts_tileset_get_tile_pixels(iso->tileset, tile);
580
581 // Move from the top diamond-corner to the top-left corner of the tile image.
582 // For regular tiles, tile height == base tile height, so the y offset is 0.
583 // For super tiles, move as high up as the height of the tile.
584 const ivec2 offset = {
585 -(iso->map->base_tile_width / 2),
586 pTile->height - iso->map->base_tile_height};
587 const ivec2 top_left = ivec2_add(screen_origin, offset);
588
589 draw_rect(
590 &iso->screen, top_left, pTile->width, pTile->height, pixels, nullptr);
591}
592
593static void draw_world(IsoGfx* iso) {
594 assert(iso);
595
596 const int W = iso->screen.width;
597 const int H = iso->screen.height;
598
599 memset(iso->screen.pixels, 0, W * H * sizeof(Pixel));
600
601 const Tm_Layer* layer = tm_map_get_layer(iso->map, 0);
602
603 // TODO: Culling.
604 // Ex: map the screen corners to tile space to cull.
605 // Ex: walk in screen space and fetch the tile.
606 // The tile-centric approach might be more cache-friendly since the
607 // screen-centric approach would juggle multiple tiles throughout the scan.
608 for (int wy = 0; wy < iso->map->world_height; ++wy) {
609 for (int wx = 0; wx < iso->map->world_width; ++wx) {
610 const Tile tile = tm_layer_get_tile(iso->map, layer, wx, wy);
611 const ivec2 screen_origin =
612 GetTileScreenOrigin(iso->iso_space, iso->camera, wx, wy);
613 draw_tile(iso, screen_origin, tile);
614 }
615 }
616}
617
618static void draw_sprite(
619 IsoGfx* iso, ivec2 origin, const SpriteInstance* sprite,
620 const Ss_SpriteSheet* sheet) {
621 assert(iso);
622 assert(sprite);
623 assert(sheet);
624 assert(sprite->animation >= 0);
625 assert(sprite->animation < sheet->num_rows);
626 assert(sprite->frame >= 0);
627
628 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation);
629 const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame);
630 draw_rect(
631 &iso->screen, origin, sheet->sprite_width, sheet->sprite_height,
632 sheet->palette.colours, frame);
633}
634
635static void draw_sprites(IsoGfx* iso) {
636 assert(iso);
637
638 for (const SpriteInstance* sprite = iso->head_sprite; sprite;
639 sprite = sprite->next) {
640 const Ss_SpriteSheet* sheet = sprite->sheet;
641 assert(sheet);
642
643 const ivec2 screen_origin = GetTileScreenOrigin(
644 iso->iso_space, iso->camera, sprite->position.x, sprite->position.y);
645 draw_sprite(iso, screen_origin, sprite, sheet);
646 }
647}
648
649void isogfx_set_camera(IsoGfx* iso, int x, int y) {
650 assert(iso);
651 iso->camera = (ivec2){x, y};
652}
653
654void isogfx_render(IsoGfx* iso) {
655 assert(iso);
656 draw_world(iso);
657 draw_sprites(iso);
658}
659
660void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) {
661 assert(iso);
662 assert(x >= 0);
663 assert(y >= 0);
664 assert(x < iso->map->world_width);
665 assert(y < iso->map->world_height);
666
667 const ivec2 screen_origin =
668 GetTileScreenOrigin(iso->iso_space, iso->camera, x, y);
669 draw_tile(iso, screen_origin, tile);
670}
671
672void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) {
673 assert(iso);
674 assert(width);
675 assert(height);
676 *width = iso->screen.width;
677 *height = iso->screen.height;
678}
679
680const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) {
681 assert(iso);
682 return iso->screen.pixels;
683}
684
685void isogfx_pick_tile(
686 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) {
687 assert(iso);
688 assert(xiso);
689 assert(yiso);
690
691 const vec2 camera = ivec2_to_vec2(iso->camera);
692 const vec2 xy_cart = vec2_add(camera, (vec2){xcart, ycart});
693
694 const vec2 xy_iso = cart2iso(
695 xy_cart, iso->map->base_tile_width, iso->map->base_tile_height,
696 iso->screen.width);
697
698 if ((0 <= xy_iso.x) && (xy_iso.x < iso->map->world_width) &&
699 (0 <= xy_iso.y) && (xy_iso.y < iso->map->world_height)) {
700 *xiso = (int)xy_iso.x;
701 *yiso = (int)xy_iso.y;
702 } else {
703 *xiso = -1;
704 *yiso = -1;
705 }
706}