aboutsummaryrefslogtreecommitdiff
path: root/gfx-iso/src
diff options
context:
space:
mode:
Diffstat (limited to 'gfx-iso/src')
-rw-r--r--gfx-iso/src/backend.c199
-rw-r--r--gfx-iso/src/isogfx.c952
2 files changed, 0 insertions, 1151 deletions
diff --git a/gfx-iso/src/backend.c b/gfx-iso/src/backend.c
deleted file mode 100644
index db91647..0000000
--- a/gfx-iso/src/backend.c
+++ /dev/null
@@ -1,199 +0,0 @@
1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h>
3
4#include <gfx/core.h>
5#include <gfx/gfx.h>
6#include <gfx/renderer.h>
7#include <gfx/scene.h>
8#include <gfx/util/geometry.h>
9#include <gfx/util/shader.h>
10
11#include <assert.h>
12#include <stdlib.h>
13
14typedef struct IsoBackend {
15 Gfx* gfx;
16 Scene* scene;
17 /// The screen or "iso screen" refers to the colour buffer of the iso graphics
18 /// library. This texture is used to draw the iso screen onto the graphics
19 /// window.
20 Texture* screen_texture;
21 /// Window size.
22 int window_width;
23 int window_height;
24 /// The viewport refers to the area inside the window to which screen_texture
25 /// is drawn. It is a scaled version of the iso screen, scaled while
26 /// respecting the iso screen's aspect ratio to prevent distortion.
27 int viewport_x, viewport_y, viewport_width, viewport_height;
28 double stretch; // Stretch factor from iso screen dimensions to viewport
29 // dimensions.
30} IsoBackend;
31
32IsoBackend* IsoBackendInit(const IsoGfx* iso) {
33 assert(iso);
34
35 IsoBackend* backend = calloc(1, sizeof(IsoBackend));
36 if (!backend) {
37 return 0;
38 }
39
40 if (!(backend->gfx = gfx_init())) {
41 goto cleanup;
42 }
43 GfxCore* gfxcore = gfx_get_core(backend->gfx);
44
45 int screen_width, screen_height;
46 isogfx_get_screen_size(iso, &screen_width, &screen_height);
47
48 if (!(backend->screen_texture = gfx_make_texture(
49 gfxcore, &(TextureDesc){
50 .width = screen_width,
51 .height = screen_height,
52 .dimension = Texture2D,
53 .format = TextureSRGBA8,
54 .filtering = NearestFiltering,
55 .wrap = ClampToEdge,
56 .mipmaps = false}))) {
57 goto cleanup;
58 }
59
60 ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore);
61 if (!shader) {
62 goto cleanup;
63 }
64
65 Geometry* geometry = gfx_make_quad_11(gfxcore);
66 if (!geometry) {
67 goto cleanup;
68 }
69
70 MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1};
71 material_desc.uniforms[0] = (ShaderUniform){
72 .type = UniformTexture,
73 .value.texture = backend->screen_texture,
74 .name = sstring_make("Texture")};
75 Material* material = gfx_make_material(&material_desc);
76 if (!material) {
77 return false;
78 }
79
80 const MeshDesc mesh_desc =
81 (MeshDesc){.geometry = geometry, .material = material, .shader = shader};
82 Mesh* mesh = gfx_make_mesh(&mesh_desc);
83 if (!mesh) {
84 goto cleanup;
85 }
86
87 SceneObject* object =
88 gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}});
89 if (!object) {
90 goto cleanup;
91 }
92
93 backend->scene = gfx_make_scene();
94 SceneNode* node = gfx_make_object_node(object);
95 SceneNode* root = gfx_get_scene_root(backend->scene);
96 gfx_set_node_parent(node, root);
97
98 return backend;
99
100cleanup:
101 if (backend->gfx) {
102 gfx_destroy(&backend->gfx);
103 }
104 free(backend);
105 return 0;
106}
107
108void IsoBackendShutdown(IsoBackend** ppApp) {
109 assert(ppApp);
110
111 IsoBackend* app = *ppApp;
112 if (!app) {
113 return;
114 }
115
116 gfx_destroy(&app->gfx);
117}
118
119void IsoBackendResizeWindow(
120 IsoBackend* app, const IsoGfx* iso, int width, int height) {
121 assert(app);
122 assert(iso);
123
124 app->window_width = width;
125 app->window_height = height;
126
127 // Virtual screen dimensions.
128 int screen_width, screen_height;
129 isogfx_get_screen_size(iso, &screen_width, &screen_height);
130
131 // Stretch the virtual screen onto the viewport while respecting the screen's
132 // aspect ratio to prevent distortion.
133 if (width > height) { // Wide screen.
134 app->stretch = (double)height / (double)screen_height;
135 app->viewport_width = (int)((double)screen_width * app->stretch);
136 app->viewport_height = height;
137 app->viewport_x = (width - app->viewport_width) / 2;
138 app->viewport_y = 0;
139 } else { // Tall screen.
140 app->stretch = (double)width / (double)screen_width;
141 app->viewport_width = width;
142 app->viewport_height = (int)((float)screen_height * app->stretch);
143 app->viewport_x = 0;
144 app->viewport_y = (height - app->viewport_height) / 2;
145 }
146}
147
148void IsoBackendRender(const IsoBackend* app, const IsoGfx* iso) {
149 assert(app);
150 assert(iso);
151
152 const Pixel* screen = isogfx_get_screen_buffer(iso);
153 assert(screen);
154 gfx_update_texture(app->screen_texture, &(TextureDataDesc){.pixels = screen});
155
156 GfxCore* gfxcore = gfx_get_core(app->gfx);
157 Renderer* renderer = gfx_get_renderer(app->gfx);
158
159 // Clear the whole window.
160 gfx_set_viewport(gfxcore, 0, 0, app->window_width, app->window_height);
161 gfx_clear(gfxcore, vec4_make(0, 0, 0, 0));
162
163 // Draw to the subregion where the virtual screen can stretch without
164 // distortion.
165 gfx_set_viewport(
166 gfxcore, app->viewport_x, app->viewport_y, app->viewport_width,
167 app->viewport_height);
168
169 // Render the iso screen.
170 gfx_start_frame(gfxcore);
171 gfx_render_scene(
172 renderer, &(RenderSceneParams){
173 .mode = RenderDefault, .scene = app->scene, .camera = 0});
174 gfx_end_frame(gfxcore);
175}
176
177bool IsoBackendGetMousePosition(
178 const IsoBackend* app, double window_x, double window_y, double* x,
179 double* y) {
180 assert(app);
181
182 // Translate from window coordinates to the subregion where the stretched
183 // iso screen is rendered.
184 const double screen_x = window_x - app->viewport_x;
185 const double screen_y = window_y - app->viewport_y;
186
187 // Position may be out of bounds.
188 if ((0 <= screen_x) && (screen_x < app->viewport_width) && (0 <= screen_y) &&
189 (screen_y < app->viewport_height)) {
190 // Scale back from the stretched subregion to the iso screen dimensions.
191 *x = screen_x / app->stretch;
192 *y = screen_y / app->stretch;
193 return true;
194 } else {
195 *x = -1;
196 *y = -1;
197 return false;
198 }
199}
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c
deleted file mode 100644
index 52c4ae2..0000000
--- a/gfx-iso/src/isogfx.c
+++ /dev/null
@@ -1,952 +0,0 @@
1#include <isogfx/isogfx.h>
2
3#include <filesystem.h>
4#include <mem.h>
5#include <mempool.h>
6#include <path.h>
7
8#include <linux/limits.h>
9
10#include <assert.h>
11#include <stdbool.h>
12#include <stdint.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16
17/// Maximum number of tiles unless the user specifies a value.
18#define DEFAULT_MAX_NUM_TILES 1024
19
20/// Maximum number of sprites unless the user specifies a value.
21#define DEFAULT_MAX_NUM_SPRITES 128
22
23/// Size of sprite sheet pool in bytes unless the user specifies a value.
24#define DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES (8 * 1024 * 1024)
25
26/// Default animation speed.
27#define ANIMATION_FPS 10
28
29/// Time between animation updates.
30#define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS)
31
32typedef struct ivec2 {
33 int x, y;
34} ivec2;
35
36typedef struct vec2 {
37 double x, y;
38} vec2;
39
40// -----------------------------------------------------------------------------
41// Tile set (TS) and tile map (TM) file formats.
42// -----------------------------------------------------------------------------
43
44/// Maximum length of path strings in .TS and .TM files.
45#define MAX_PATH_LENGTH 128
46
47typedef struct Ts_Tile {
48 uint16_t width; /// Tile width in pixels.
49 uint16_t height; /// Tile height in pixels.
50 Pixel pixels[1]; /// Count: width * height.
51} Ts_Tile;
52
53typedef struct Ts_TileSet {
54 uint16_t num_tiles;
55 uint16_t max_tile_width; /// Maximum tile width in pixels.
56 uint16_t max_tile_height; /// Maximum tile height in pixels.
57 Ts_Tile tiles[1]; /// Count: num_tiles.
58} Ts_TileSet;
59
60typedef struct Tm_Layer {
61 union {
62 char tileset_path[MAX_PATH_LENGTH]; // Relative to the Tm_Map file.
63 };
64 Tile tiles[1]; /// Count: world_width * world_height.
65} Tm_Layer;
66
67typedef struct Tm_Map {
68 uint16_t world_width; /// World width in number of tiles.
69 uint16_t world_height; /// World height in number of tiles.
70 uint16_t base_tile_width;
71 uint16_t base_tile_height;
72 uint16_t num_layers;
73 Tm_Layer layers[1]; // Count: num_layers.
74} Tm_Map;
75
76static inline const Tm_Layer* tm_map_get_next_layer(
77 const Tm_Map* map, const Tm_Layer* layer) {
78 assert(map);
79 assert(layer);
80 return (const Tm_Layer*)((const uint8_t*)layer + sizeof(Tm_Layer) +
81 ((map->world_width * map->world_height - 1) *
82 sizeof(Tile)));
83}
84
85static inline const Ts_Tile* ts_tileset_get_next_tile(
86 const Ts_TileSet* tileset, const Ts_Tile* tile) {
87 assert(tileset);
88 assert(tile);
89 return (const Ts_Tile*)((const uint8_t*)tile + sizeof(Ts_Tile) +
90 ((tile->width * tile->height - 1) * sizeof(Pixel)));
91}
92
93// -----------------------------------------------------------------------------
94// Sprite sheet file format.
95// -----------------------------------------------------------------------------
96
97/// A row of sprites in a sprite sheet.
98///
99/// Each row in a sprite sheet can have a different number of columns.
100///
101/// The pixels of the row follow a "sprite-major" order. It contains the
102/// 'sprite_width * sprite_height' pixels for the first column/sprite, then the
103/// second column/sprite, etc.
104///
105/// Pixels are 8-bit indices into the sprite sheet's colour palette.
106typedef struct Ss_Row {
107 uint16_t num_cols; /// Number of columns in this row.
108 uint8_t pixels[1]; /// Count: num_cols * sprite_width * sprite_height.
109} Ss_Row;
110
111typedef struct Ss_Palette {
112 uint16_t num_colours;
113 Pixel colours[1]; /// Count: num_colors.
114} Ss_Palette;
115
116/// Sprite sheet top-level data definition.
117///
118/// Sprite width and height are assumed constant throughout the sprite sheet.
119typedef struct Ss_SpriteSheet {
120 uint16_t sprite_width; /// Sprite width in pixels.
121 uint16_t sprite_height; /// Sprite height in pixels.
122 uint16_t num_rows;
123 Ss_Palette palette; /// Variable size.
124 Ss_Row rows[1]; /// Count: num_rows. Variable offset.
125} Ss_SpriteSheet;
126
127static inline const Ss_Row* get_sprite_sheet_row(
128 const Ss_SpriteSheet* sheet, int row) {
129 assert(sheet);
130 assert(row >= 0);
131 assert(row < sheet->num_rows);
132 // Skip over the palette.
133 const Ss_Row* rows =
134 (const Ss_Row*)(&sheet->palette.colours[0] + sheet->palette.num_colours);
135 return &rows[row];
136}
137
138static inline const uint8_t* get_sprite_sheet_sprite(
139 const Ss_SpriteSheet* sheet, const Ss_Row* row, int col) {
140 assert(sheet);
141 assert(row);
142 assert(col >= 0);
143 assert(col < row->num_cols);
144 const int sprite_offset = col * sheet->sprite_width * sheet->sprite_height;
145 const uint8_t* sprite = &row->pixels[sprite_offset];
146 return sprite;
147}
148
149// -----------------------------------------------------------------------------
150// Renderer state.
151// -----------------------------------------------------------------------------
152
153typedef struct TileData {
154 uint16_t width;
155 uint16_t height;
156 uint16_t pixels_handle; // Handle to the tile's pixels in the pixel pool.
157} TileData;
158
159// File format is already convenient for working in memory.
160typedef Ss_Row SpriteSheetRow;
161typedef Ss_SpriteSheet SpriteSheetData;
162
163typedef struct SpriteData {
164 SpriteSheet sheet; // Handle to the sprite's sheet.
165 ivec2 position;
166 int animation; // Current animation.
167 int frame; // Current frame of animation.
168} SpriteData;
169
170DEF_MEMPOOL_DYN(TilePool, TileData)
171DEF_MEM_DYN(PixelPool, Pixel)
172
173DEF_MEMPOOL_DYN(SpritePool, SpriteData)
174DEF_MEM_DYN(SpriteSheetPool, SpriteSheetData)
175
176typedef struct IsoGfx {
177 int screen_width;
178 int screen_height;
179 int tile_width;
180 int tile_height;
181 int world_width;
182 int world_height;
183 int max_num_sprites;
184 int sprite_sheet_pool_size_bytes;
185 double last_animation_time;
186 Tile* world;
187 Pixel* screen;
188 TilePool tiles;
189 PixelPool pixels;
190 SpritePool sprites;
191 SpriteSheetPool sheets;
192} IsoGfx;
193
194// -----------------------------------------------------------------------------
195// Math and world / tile / screen access.
196// -----------------------------------------------------------------------------
197
198static inline ivec2 ivec2_add(ivec2 a, ivec2 b) {
199 return (ivec2){.x = a.x + b.x, .y = a.y + b.y};
200}
201
202static inline ivec2 ivec2_scale(ivec2 a, int s) {
203 return (ivec2){.x = a.x * s, .y = a.y * s};
204}
205
206static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) {
207 return (ivec2){
208 .x = (iso.x - iso.y) * (s / 2) + (w / 2), .y = (iso.x + iso.y) * (t / 2)};
209}
210
211// Method 1.
212// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
213// const double x = cart.x - (double)(w / 2);
214// const double xiso = (x * t + cart.y * s) / (double)(s * t);
215// return (vec2){
216// .x = (int)(xiso), .y = (int)((2.0 / (double)t) * cart.y - xiso)};
217//}
218
219// Method 2.
220static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
221 const double one_over_s = 1. / (double)s;
222 const double one_over_t = 1. / (double)t;
223 const double x = cart.x - (double)(w / 2);
224 return (vec2){
225 .x = (one_over_s * x + one_over_t * cart.y),
226 .y = (-one_over_s * x + one_over_t * cart.y)};
227}
228
229static const Pixel* tile_xy_const_ref(
230 const IsoGfx* iso, const TileData* tile, int x, int y) {
231 assert(iso);
232 assert(tile);
233 assert(x >= 0);
234 assert(y >= 0);
235 assert(x < tile->width);
236 assert(y < tile->height);
237 return &mem_get_chunk(&iso->pixels, tile->pixels_handle)[y * tile->width + x];
238}
239
240// static Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) {
241// return *tile_xy_const_ref(iso, tile, x, y);
242// }
243
244static Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) {
245 return (Pixel*)tile_xy_const_ref(iso, tile, x, y);
246}
247
248static inline const Tile* world_xy_const_ref(const IsoGfx* iso, int x, int y) {
249 assert(iso);
250 assert(x >= 0);
251 assert(y >= 0);
252 assert(x < iso->world_width);
253 assert(y < iso->world_height);
254 return &iso->world[y * iso->world_width + x];
255}
256
257static inline Tile world_xy(const IsoGfx* iso, int x, int y) {
258 return *world_xy_const_ref(iso, x, y);
259}
260
261static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
262 return (Tile*)world_xy_const_ref(iso, x, y);
263}
264
265static inline const Pixel* screen_xy_const_ref(
266 const IsoGfx* iso, int x, int y) {
267 assert(iso);
268 assert(x >= 0);
269 assert(y >= 0);
270 assert(x < iso->screen_width);
271 assert(y < iso->screen_height);
272 return &iso->screen[y * iso->screen_width + x];
273}
274
275static inline Pixel screen_xy(IsoGfx* iso, int x, int y) {
276 return *screen_xy_const_ref(iso, x, y);
277}
278
279static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) {
280 return (Pixel*)screen_xy_const_ref(iso, x, y);
281}
282
283static int calc_num_tile_blocks(
284 int base_tile_width, int base_tile_height, int tile_width,
285 int tile_height) {
286 const int base_tile_size = base_tile_width * base_tile_height;
287 const int tile_size = tile_width * tile_height;
288 const int num_blocks = tile_size / base_tile_size;
289 return num_blocks;
290}
291
292// -----------------------------------------------------------------------------
293// Renderer, world and tile management.
294// -----------------------------------------------------------------------------
295
296IsoGfx* isogfx_new(const IsoGfxDesc* desc) {
297 assert(desc->screen_width > 0);
298 assert(desc->screen_height > 0);
299 // Part of our implementation assumes even widths and heights for precision.
300 assert((desc->screen_width & 1) == 0);
301 assert((desc->screen_height & 1) == 0);
302
303 IsoGfx* iso = calloc(1, sizeof(IsoGfx));
304 if (!iso) {
305 return 0;
306 }
307
308 iso->screen_width = desc->screen_width;
309 iso->screen_height = desc->screen_height;
310
311 iso->last_animation_time = 0.0;
312
313 iso->max_num_sprites = desc->max_num_sprites == 0 ? DEFAULT_MAX_NUM_SPRITES
314 : desc->max_num_sprites;
315 iso->sprite_sheet_pool_size_bytes = desc->sprite_sheet_pool_size_bytes == 0
316 ? DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES
317 : desc->sprite_sheet_pool_size_bytes;
318
319 const int screen_size = desc->screen_width * desc->screen_height;
320 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) {
321 goto cleanup;
322 }
323
324 return iso;
325
326cleanup:
327 isogfx_del(&iso);
328 return 0;
329}
330
331/// Destroy the world, its tile set, and the underlying pools.
332static void destroy_world(IsoGfx* iso) {
333 assert(iso);
334 if (iso->world) {
335 free(iso->world);
336 iso->world = 0;
337 }
338 mempool_del(&iso->tiles);
339 mem_del(&iso->pixels);
340}
341
342/// Destroy all loaded sprites and the underlying pools.
343static void destroy_sprites(IsoGfx* iso) {
344 assert(iso);
345 mempool_del(&iso->sprites);
346 mem_del(&iso->sheets);
347}
348
349void isogfx_del(IsoGfx** pIso) {
350 assert(pIso);
351 IsoGfx* iso = *pIso;
352 if (iso) {
353 destroy_world(iso);
354 destroy_sprites(iso);
355 if (iso->screen) {
356 free(iso->screen);
357 iso->screen = 0;
358 }
359 free(iso);
360 *pIso = 0;
361 }
362}
363
364bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) {
365 assert(iso);
366 assert(desc);
367 assert(desc->tile_width > 0);
368 assert(desc->tile_height > 0);
369 // Part of our implementation assumes even widths and heights for greater
370 // precision.
371 assert((desc->tile_width & 1) == 0);
372 assert((desc->tile_height & 1) == 0);
373
374 // Handle recreation by destroying the previous world.
375 destroy_world(iso);
376
377 iso->tile_width = desc->tile_width;
378 iso->tile_height = desc->tile_height;
379 iso->world_width = desc->world_width;
380 iso->world_height = desc->world_height;
381
382 const int world_size = desc->world_width * desc->world_height;
383 const int tile_size = desc->tile_width * desc->tile_height;
384 const int tile_size_bytes = tile_size * (int)sizeof(Pixel);
385 const int tile_pool_size =
386 desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES;
387
388 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
389 goto cleanup;
390 }
391 if (!mempool_make_dyn(&iso->tiles, world_size, sizeof(TileData))) {
392 goto cleanup;
393 }
394 if (!mem_make_dyn(&iso->pixels, tile_pool_size, tile_size_bytes)) {
395 goto cleanup;
396 }
397
398 return true;
399
400cleanup:
401 destroy_world(iso);
402 return false;
403}
404
405bool isogfx_load_world(IsoGfx* iso, const char* filepath) {
406 assert(iso);
407 assert(filepath);
408
409 bool success = false;
410
411 // Handle recreation by destroying the previous world.
412 destroy_world(iso);
413
414 // Load the map.
415 printf("Load tile map: %s\n", filepath);
416 Tm_Map* map = read_file(filepath);
417 if (!map) {
418 goto cleanup;
419 }
420
421 // Allocate memory for the map and tile sets.
422 const int world_size = map->world_width * map->world_height;
423 const int base_tile_size = map->base_tile_width * map->base_tile_height;
424 const int base_tile_size_bytes = base_tile_size * (int)sizeof(Pixel);
425 // TODO: Need to get the total number of tiles from the map.
426 const int tile_pool_size = DEFAULT_MAX_NUM_TILES;
427
428 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
429 goto cleanup;
430 }
431 if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) {
432 goto cleanup;
433 }
434 if (!mem_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) {
435 goto cleanup;
436 }
437
438 // Load the tile sets.
439 const Tm_Layer* layer = &map->layers[0];
440 // TODO: Handle num_layers layers.
441 for (int i = 0; i < 1; ++i) {
442 const char* ts_path = layer->tileset_path;
443
444 // Tile set path is relative to the tile map file. Make it relative to the
445 // current working directory before loading.
446 char ts_path_cwd[PATH_MAX] = {0};
447 if (!path_make_relative(filepath, ts_path, ts_path_cwd, PATH_MAX)) {
448 goto cleanup;
449 }
450
451 Ts_TileSet* tileset = read_file(ts_path_cwd);
452 if (!tileset) {
453 goto cleanup;
454 };
455
456 // Load tile data.
457 const Ts_Tile* tile = &tileset->tiles[0];
458 for (uint16_t j = 0; j < tileset->num_tiles; ++j) {
459 // Tile dimensions should be a multiple of the base tile size.
460 assert((tile->width % map->base_tile_width) == 0);
461 assert((tile->height % map->base_tile_height) == 0);
462
463 // Allocate N base tile size blocks for the tile.
464 const uint16_t tile_size = tile->width * tile->height;
465 const int num_blocks = tile_size / base_tile_size;
466 Pixel* pixels = mem_alloc(&iso->pixels, num_blocks);
467 assert(pixels);
468 memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel));
469
470 // Allocate the tile data.
471 TileData* tile_data = mempool_alloc(&iso->tiles);
472 assert(tile_data);
473 tile_data->width = tile->width;
474 tile_data->height = tile->height;
475 tile_data->pixels_handle =
476 (uint16_t)mem_get_chunk_handle(&iso->pixels, pixels);
477
478 tile = ts_tileset_get_next_tile(tileset, tile);
479 }
480
481 printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd);
482
483 free(tileset);
484 layer = tm_map_get_next_layer(map, layer);
485 }
486
487 // Load the map into the world.
488 layer = &map->layers[0];
489 // TODO: Handle num_layers layers.
490 for (int i = 0; i < 1; ++i) {
491 memcpy(iso->world, layer->tiles, world_size * sizeof(Tile));
492
493 // TODO: We need to handle 'firsgid' in TMX files.
494 for (int j = 0; j < world_size; ++j) {
495 iso->world[j] -= 1;
496 }
497
498 layer = tm_map_get_next_layer(map, layer);
499 }
500
501 iso->world_width = map->world_width;
502 iso->world_height = map->world_height;
503 iso->tile_width = map->base_tile_width;
504 iso->tile_height = map->base_tile_height;
505
506 success = true;
507
508cleanup:
509 if (map) {
510 free(map);
511 }
512 if (!success) {
513 destroy_world(iso);
514 }
515 return success;
516}
517
518int isogfx_world_width(const IsoGfx* iso) {
519 assert(iso);
520 return iso->world_width;
521}
522
523int isogfx_world_height(const IsoGfx* iso) {
524 assert(iso);
525 return iso->world_height;
526}
527
528/// Create a tile mask procedurally.
529static void make_tile_from_colour(
530 const IsoGfx* iso, Pixel colour, TileData* tile) {
531 assert(iso);
532 assert(tile);
533
534 const int width = tile->width;
535 const int height = tile->height;
536 const int r = width / height;
537
538 for (int y = 0; y < height / 2; ++y) {
539 const int mask_start = width / 2 - r * y - 1;
540 const int mask_end = width / 2 + r * y + 1;
541 for (int x = 0; x < width; ++x) {
542 const bool mask = (mask_start <= x) && (x <= mask_end);
543 const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0};
544
545 // Top half.
546 *tile_xy_mut(iso, tile, x, y) = val;
547
548 // Bottom half reflects the top half.
549 const int y_reflected = height - y - 1;
550 *tile_xy_mut(iso, tile, x, y_reflected) = val;
551 }
552 }
553}
554
555Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
556 assert(iso);
557 assert(desc);
558 // Client must create world before creating tiles.
559 assert(iso->tile_width > 0);
560 assert(iso->tile_height > 0);
561
562 TileData* tile = mempool_alloc(&iso->tiles);
563 assert(tile); // TODO: Make this a hard assert.
564
565 const int num_blocks = calc_num_tile_blocks(
566 iso->tile_width, iso->tile_height, desc->width, desc->height);
567
568 Pixel* pixels = mem_alloc(&iso->pixels, num_blocks);
569 assert(pixels); // TODO: Make this a hard assert.
570
571 tile->width = desc->width;
572 tile->height = desc->height;
573 tile->pixels_handle = mem_get_chunk_handle(&iso->pixels, pixels);
574
575 switch (desc->type) {
576 case TileFromColour:
577 make_tile_from_colour(iso, desc->colour, tile);
578 break;
579 case TileFromFile:
580 assert(false); // TODO
581 break;
582 case TileFromMemory:
583 assert(false); // TODO
584 break;
585 }
586
587 return (Tile)mempool_get_block_index(&iso->tiles, tile);
588}
589
590void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) {
591 assert(iso);
592 *world_xy_mut(iso, x, y) = tile;
593}
594
595void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) {
596 assert(iso);
597 for (int y = y0; y < y1; ++y) {
598 for (int x = x0; x < x1; ++x) {
599 isogfx_set_tile(iso, x, y, tile);
600 }
601 }
602}
603
604bool isogfx_load_sprite_sheet(
605 IsoGfx* iso, const char* filepath, SpriteSheet* p_sheet) {
606 assert(iso);
607 assert(filepath);
608 assert(p_sheet);
609
610 bool success = false;
611
612 // Lazy initialization of sprite pools.
613 if (mempool_capacity(&iso->sprites) == 0) {
614 if (!mempool_make_dyn(
615 &iso->sprites, iso->max_num_sprites, sizeof(SpriteData))) {
616 return false;
617 }
618 }
619 if (mem_capacity(&iso->sheets) == 0) {
620 // Using a block size of 1 byte for sprite sheet data.
621 if (!mem_make_dyn(&iso->sheets, iso->sprite_sheet_pool_size_bytes, 1)) {
622 return false;
623 }
624 }
625
626 // Load sprite sheet file.
627 printf("Load sprite sheet: %s\n", filepath);
628 FILE* file = fopen(filepath, "rb");
629 if (file == NULL) {
630 goto cleanup;
631 }
632 const size_t sheet_size = get_file_size(file);
633 SpriteSheetData* ss_sheet = mem_alloc(&iso->sheets, sheet_size);
634 if (!ss_sheet) {
635 goto cleanup;
636 }
637 if (fread(ss_sheet, sheet_size, 1, file) != 1) {
638 goto cleanup;
639 }
640
641 *p_sheet = mem_get_chunk_handle(&iso->sheets, ss_sheet);
642 success = true;
643
644cleanup:
645 // Pools remain initialized since client may attempt to load other sprites.
646 if (file != NULL) {
647 fclose(file);
648 }
649 if (!success) {
650 if (ss_sheet) {
651 mem_free(&iso->sheets, &ss_sheet);
652 }
653 }
654 return success;
655}
656
657Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) {
658 assert(iso);
659
660 SpriteData* sprite = mempool_alloc(&iso->sprites);
661 assert(sprite);
662
663 sprite->sheet = sheet;
664
665 return mempool_get_block_index(&iso->sprites, sprite);
666}
667
668#define with_sprite(SPRITE, BODY) \
669 { \
670 SpriteData* data = mempool_get_block(&iso->sprites, sprite); \
671 assert(data); \
672 BODY; \
673 }
674
675void isogfx_set_sprite_position(IsoGfx* iso, Sprite sprite, int x, int y) {
676 assert(iso);
677 with_sprite(sprite, {
678 data->position.x = x;
679 data->position.y = y;
680 });
681}
682
683void isogfx_set_sprite_animation(IsoGfx* iso, Sprite sprite, int animation) {
684 assert(iso);
685 with_sprite(sprite, { data->animation = animation; });
686}
687
688void isogfx_update(IsoGfx* iso, double t) {
689 assert(iso);
690
691 // If this is the first time update() is called after initialization, just
692 // record the starting animation time.
693 if (iso->last_animation_time == 0.0) {
694 iso->last_animation_time = t;
695 return;
696 }
697
698 if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) {
699 // TODO: Consider linking animated sprites in a list so that we only walk
700 // over those here and not also the static sprites.
701 mempool_foreach(&iso->sprites, sprite, {
702 const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet);
703 assert(sheet); // TODO: Make this a hard assert inside the mem/pool.
704 const SpriteSheetRow* row =
705 get_sprite_sheet_row(sheet, sprite->animation);
706 sprite->frame = (sprite->frame + 1) % row->num_cols;
707 });
708
709 iso->last_animation_time = t;
710 }
711}
712
713// -----------------------------------------------------------------------------
714// Rendering and picking.
715// -----------------------------------------------------------------------------
716
717typedef struct CoordSystem {
718 ivec2 o; /// Origin.
719 ivec2 x;
720 ivec2 y;
721} CoordSystem;
722
723/// Create the basis for the isometric coordinate system with origin and vectors
724/// expressed in the Cartesian system.
725static CoordSystem make_iso_coord_system(const IsoGfx* iso) {
726 assert(iso);
727 const ivec2 o = {iso->screen_width / 2, 0};
728 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
729 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
730 return (CoordSystem){o, x, y};
731}
732
733/// Get the screen position of the top diamond-corner of the tile at world
734/// (x,y).
735static ivec2 GetTileScreenOrigin(
736 const CoordSystem iso_space, int world_x, int world_y) {
737 const ivec2 vx_offset = ivec2_scale(iso_space.x, world_x);
738 const ivec2 vy_offset = ivec2_scale(iso_space.y, world_y);
739 const ivec2 screen_origin =
740 ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset));
741
742 return screen_origin;
743}
744
745static Pixel alpha_blend(Pixel src, Pixel dst) {
746 if ((src.a == 255) || (dst.a == 0)) {
747 return src;
748 }
749 const uint16_t one_minus_alpha = 255 - src.a;
750#define blend(s, d) \
751 (Channel)( \
752 (double)((uint16_t)s * (uint16_t)src.a + \
753 (uint16_t)d * one_minus_alpha) / \
754 255.0)
755 return (Pixel){
756 .r = blend(src.r, dst.r),
757 .g = blend(src.g, dst.g),
758 .b = blend(src.b, dst.b),
759 .a = src.a};
760}
761
762/// Draw a rectangle (tile or sprite).
763///
764/// The rectangle's top-left corner is mapped to the screen space position given
765/// by 'top_left'.
766///
767/// The rectangle's pixels are assumed to be arranged in a linear, row-major
768/// fashion.
769///
770/// If indices are given, then the image is assumed to be colour-paletted, where
771/// 'pixels' is the palette and 'indices' the pixel indices. Otherwise, the
772/// image is assumed to be in plain RGBA format.
773static void draw_rect(
774 IsoGfx* iso, ivec2 top_left, int rect_width, int rect_height,
775 const Pixel* pixels, const uint8_t* indices) {
776 assert(iso);
777
778#define rect_pixel(X, Y) \
779 (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X])
780
781 // Rect origin can be outside screen bounds, so we must offset accordingly to
782 // draw only the visible portion.
783#define max(a, b) (a > b ? a : b)
784 const int px_offset = max(0, -top_left.x);
785 const int py_offset = max(0, -top_left.y);
786
787 // Rect can exceed screen bounds, so clip along Y and X as we draw.
788 for (int py = py_offset;
789 (py < rect_height) && (top_left.y + py < iso->screen_height); ++py) {
790 const int sy = top_left.y + py;
791 for (int px = px_offset;
792 (px < rect_width) && (top_left.x + px < iso->screen_width); ++px) {
793 const Pixel colour = rect_pixel(px, py);
794 if (colour.a > 0) {
795 const int sx = top_left.x + px;
796 const Pixel dst = screen_xy(iso, sx, sy);
797 const Pixel final = alpha_blend(colour, dst);
798 *screen_xy_mut(iso, sx, sy) = final;
799 }
800 }
801 }
802}
803
804/// Draw a tile.
805///
806/// 'screen_origin' is the screen coordinates of the top diamond-corner of the
807/// tile (the base tile for super tiles).
808/// World (0, 0) -> (screen_width / 2, 0).
809static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) {
810 assert(iso);
811
812 const TileData* tile_data = mempool_get_block(&iso->tiles, tile);
813 assert(tile_data);
814 const Pixel* pixels = tile_xy_const_ref(iso, tile_data, 0, 0);
815
816 // Move from the top diamond-corner to the top-left corner of the tile image.
817 // For regular tiles, tile height == base tile height, so the y offset is 0.
818 // For super tiles, move as high up as the height of the tile.
819 const ivec2 offset = {
820 -(iso->tile_width / 2), tile_data->height - iso->tile_height};
821 const ivec2 top_left = ivec2_add(screen_origin, offset);
822
823 draw_rect(iso, top_left, tile_data->width, tile_data->height, pixels, 0);
824}
825
826static void draw_world(IsoGfx* iso) {
827 assert(iso);
828
829 const int W = iso->screen_width;
830 const int H = iso->screen_height;
831
832 memset(iso->screen, 0, W * H * sizeof(Pixel));
833
834 const CoordSystem iso_space = make_iso_coord_system(iso);
835
836 // TODO: Culling.
837 // Ex: map the screen corners to tile space to cull.
838 // Ex: walk in screen space and fetch the tile.
839 // The tile-centric approach might be more cache-friendly since the
840 // screen-centric approach would juggle multiple tiles throughout the scan.
841 for (int wy = 0; wy < iso->world_height; ++wy) {
842 for (int wx = 0; wx < iso->world_width; ++wx) {
843 const Tile tile = world_xy(iso, wx, wy);
844 const ivec2 screen_origin = GetTileScreenOrigin(iso_space, wx, wy);
845 draw_tile(iso, screen_origin, tile);
846 }
847 }
848}
849
850static void draw_sprite(
851 IsoGfx* iso, ivec2 origin, const SpriteData* sprite,
852 const SpriteSheetData* sheet) {
853 assert(iso);
854 assert(sprite);
855 assert(sheet);
856 assert(sprite->animation >= 0);
857 assert(sprite->animation < sheet->num_rows);
858 assert(sprite->frame >= 0);
859
860 const SpriteSheetRow* row = get_sprite_sheet_row(sheet, sprite->animation);
861 const uint8_t* frame = get_sprite_sheet_sprite(sheet, row, sprite->frame);
862 draw_rect(
863 iso, origin, sheet->sprite_width, sheet->sprite_height,
864 sheet->palette.colours, frame);
865}
866
867static void draw_sprites(IsoGfx* iso) {
868 assert(iso);
869
870 const CoordSystem iso_space = make_iso_coord_system(iso);
871
872 mempool_foreach(&iso->sprites, sprite, {
873 const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet);
874 assert(sheet);
875
876 const ivec2 screen_origin =
877 GetTileScreenOrigin(iso_space, sprite->position.x, sprite->position.y);
878 draw_sprite(iso, screen_origin, sprite, sheet);
879 });
880}
881
882void isogfx_render(IsoGfx* iso) {
883 assert(iso);
884 draw_world(iso);
885 draw_sprites(iso);
886}
887
888void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) {
889 assert(iso);
890 assert(x >= 0);
891 assert(y >= 0);
892 assert(x < iso->world_width);
893 assert(y < iso->world_height);
894
895 const CoordSystem iso_space = make_iso_coord_system(iso);
896 const ivec2 screen_origin = GetTileScreenOrigin(iso_space, x, y);
897 draw_tile(iso, screen_origin, tile);
898}
899
900bool isogfx_resize(IsoGfx* iso, int screen_width, int screen_height) {
901 assert(iso);
902 assert(iso->screen);
903
904 const int current_size = iso->screen_width * iso->screen_height;
905 const int new_size = screen_width * screen_height;
906
907 if (new_size > current_size) {
908 Pixel* new_screen = calloc(new_size, sizeof(Pixel));
909 if (new_screen) {
910 free(iso->screen);
911 iso->screen = new_screen;
912 } else {
913 return false;
914 }
915 }
916 iso->screen_width = screen_width;
917 iso->screen_height = screen_height;
918 return true;
919}
920
921void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) {
922 assert(iso);
923 assert(width);
924 assert(height);
925 *width = iso->screen_width;
926 *height = iso->screen_height;
927}
928
929const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) {
930 assert(iso);
931 return iso->screen;
932}
933
934void isogfx_pick_tile(
935 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) {
936 assert(iso);
937 assert(xiso);
938 assert(yiso);
939
940 const vec2 xy_iso = cart2iso(
941 (vec2){.x = xcart, .y = ycart}, iso->tile_width, iso->tile_height,
942 iso->screen_width);
943
944 if ((0 <= xy_iso.x) && (xy_iso.x < iso->world_width) && (0 <= xy_iso.y) &&
945 (xy_iso.y < iso->world_height)) {
946 *xiso = (int)xy_iso.x;
947 *yiso = (int)xy_iso.y;
948 } else {
949 *xiso = -1;
950 *yiso = -1;
951 }
952}