summaryrefslogtreecommitdiff
path: root/gfx-iso/src
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-07-08 14:37:29 -0700
committer3gg <3gg@shellblade.net>2023-07-08 14:37:29 -0700
commit21a0d0c1c424f7db90c3282aad4bf6ad4ef809b7 (patch)
treea6ae8a8cb4108cd33713178e67d3b482fc1fd5ee /gfx-iso/src
parent303f5dc58dd8e8266df3c62fc84d9799db8047b9 (diff)
Load tile maps and tile sets from files.
Diffstat (limited to 'gfx-iso/src')
-rw-r--r--gfx-iso/src/isogfx.c561
1 files changed, 401 insertions, 160 deletions
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c
index b38efe7..17b88b2 100644
--- a/gfx-iso/src/isogfx.c
+++ b/gfx-iso/src/isogfx.c
@@ -1,36 +1,106 @@
1#include <isogfx/isogfx.h> 1#include <isogfx/isogfx.h>
2 2
3#include <filesystem.h>
3#include <mempool.h> 4#include <mempool.h>
4 5
6#include <linux/limits.h>
7
5#include <assert.h> 8#include <assert.h>
6#include <stdbool.h> 9#include <stdbool.h>
7#include <stdint.h> 10#include <stdint.h>
11#include <stdio.h>
8#include <stdlib.h> 12#include <stdlib.h>
9#include <string.h> 13#include <string.h>
10 14
11/// Maximum number of tiles unless the user chooses a non-zero value. 15/// Maximum number of tiles unless the user chooses a non-zero value.
12#define DEFAULT_MAX_NUM_TILES 1024 16#define DEFAULT_MAX_NUM_TILES 1024
13 17
18// -----------------------------------------------------------------------------
19// Tile set (TS) and tile map (TM) file formats.
20// -----------------------------------------------------------------------------
21
22/// Maximum length of path strings in .TS and .TM files.
23#define MAX_PATH_LENGTH 128
24
25typedef struct Ts_Tile {
26 uint16_t width; /// Tile width in pixels.
27 uint16_t height; /// Tile height in pixels.
28 Pixel pixels[1]; /// Count: width * height.
29} Ts_Tile;
30
31typedef struct Ts_TileSet {
32 uint16_t num_tiles;
33 uint16_t max_tile_width; /// Maximum tile width in pixels.
34 uint16_t max_tile_height; /// Maximum tile height in pixels.
35 Ts_Tile tiles[1]; /// Count: num_tiles.
36} Ts_TileSet;
37
38typedef struct Tm_Layer {
39 union {
40 char tileset_path[MAX_PATH_LENGTH]; // Relative to the Tm_Map file.
41 };
42 Tile tiles[1]; /// Count: world_width * world_height.
43} Tm_Layer;
44
45typedef struct Tm_Map {
46 uint16_t world_width; /// World width in number of tiles.
47 uint16_t world_height; /// World height in number of tiles.
48 uint16_t base_tile_width;
49 uint16_t base_tile_height;
50 uint16_t num_layers;
51 Tm_Layer layers[1]; // Count: num_layers.
52} Tm_Map;
53
54static inline const Tm_Layer* tm_map_get_next_layer(
55 const Tm_Map* map, const Tm_Layer* layer) {
56 assert(map);
57 assert(layer);
58 return (const Tm_Layer*)((const uint8_t*)layer + sizeof(Tm_Layer) +
59 ((map->world_width * map->world_height - 1) *
60 sizeof(Tile)));
61}
62
63static inline const Ts_Tile* ts_tileset_get_next_tile(
64 const Ts_TileSet* tileset, const Ts_Tile* tile) {
65 assert(tileset);
66 assert(tile);
67 return (const Ts_Tile*)((const uint8_t*)tile + sizeof(Ts_Tile) +
68 ((tile->width * tile->height - 1) * sizeof(Pixel)));
69}
70
71// -----------------------------------------------------------------------------
72// Renderer state.
73// -----------------------------------------------------------------------------
74
75// typedef Ts_Tile TileData;
76
14typedef struct TileData { 77typedef struct TileData {
15 Pixel pixels[1]; // Dynamically allocated. 78 uint16_t width;
79 uint16_t height;
80 uint16_t num_blocks; // Number of pixel blocks in the pixels mempool.
81 uint16_t pixels_index; // Offset into the pixels mempool.
16} TileData; 82} TileData;
17 83
18DEF_MEMPOOL_DYN(TilePool, TileData) 84DEF_MEMPOOL_DYN(TilePool, TileData)
85DEF_MEMPOOL_DYN(PixelPool, Pixel)
19 86
20typedef struct IsoGfx { 87typedef struct IsoGfx {
21 Tile* world; 88 int screen_width;
22 Pixel* screen; 89 int screen_height;
23 uint8_t* tile_mask; 90 int tile_width;
24 TilePool tiles; 91 int tile_height;
25 int screen_width; 92 int world_width;
26 int screen_height; 93 int world_height;
27 int tile_width; 94 Tile* world;
28 int tile_height; 95 Pixel* screen;
29 int world_width; 96 TilePool tiles;
30 int world_height; 97 PixelPool pixels;
31 int max_num_tiles;
32} IsoGfx; 98} IsoGfx;
33 99
100// -----------------------------------------------------------------------------
101// Math and world / tile / screen access.
102// -----------------------------------------------------------------------------
103
34typedef struct ivec2 { 104typedef struct ivec2 {
35 int x, y; 105 int x, y;
36} ivec2; 106} ivec2;
@@ -70,38 +140,27 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
70 .y = (-one_over_s * x + one_over_t * cart.y)}; 140 .y = (-one_over_s * x + one_over_t * cart.y)};
71} 141}
72 142
73Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { 143static const Pixel* tile_xy_const_ref(
144 const IsoGfx* iso, const TileData* tile, int x, int y) {
74 assert(iso); 145 assert(iso);
75 assert(tile); 146 assert(tile);
76 assert(tile->pixels);
77 assert(x >= 0); 147 assert(x >= 0);
78 assert(y >= 0); 148 assert(y >= 0);
79 assert(x < iso->tile_width); 149 assert(x < tile->width);
80 assert(y < iso->tile_height); 150 assert(y < tile->height);
81 return &tile->pixels[y * iso->tile_width + x]; 151 return &mempool_get_block(
152 &iso->pixels, tile->pixels_index)[y * tile->width + x];
82} 153}
83 154
84Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { 155static Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) {
85 assert(iso); 156 return *tile_xy_const_ref(iso, tile, x, y);
86 assert(tile);
87 assert(tile->pixels);
88 assert(x >= 0);
89 assert(y >= 0);
90 assert(x < iso->tile_width);
91 assert(y < iso->tile_height);
92 return tile->pixels[y * iso->tile_width + x];
93} 157}
94 158
95static inline Tile world_xy(IsoGfx* iso, int x, int y) { 159static Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) {
96 assert(iso); 160 return (Pixel*)tile_xy_const_ref(iso, tile, x, y);
97 assert(x >= 0);
98 assert(y >= 0);
99 assert(x < iso->world_width);
100 assert(y < iso->world_height);
101 return iso->world[y * iso->world_width + x];
102} 161}
103 162
104static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { 163static inline const Tile* world_xy_const_ref(const IsoGfx* iso, int x, int y) {
105 assert(iso); 164 assert(iso);
106 assert(x >= 0); 165 assert(x >= 0);
107 assert(y >= 0); 166 assert(y >= 0);
@@ -110,16 +169,16 @@ static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
110 return &iso->world[y * iso->world_width + x]; 169 return &iso->world[y * iso->world_width + x];
111} 170}
112 171
113static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { 172static inline Tile world_xy(const IsoGfx* iso, int x, int y) {
114 assert(iso); 173 return *world_xy_const_ref(iso, x, y);
115 assert(x >= 0);
116 assert(y >= 0);
117 assert(x < iso->screen_width);
118 assert(y < iso->screen_height);
119 return iso->screen[y * iso->screen_width + x];
120} 174}
121 175
122static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { 176static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
177 return (Tile*)world_xy_const_ref(iso, x, y);
178}
179
180static inline const Pixel* screen_xy_const_ref(
181 const IsoGfx* iso, int x, int y) {
123 assert(iso); 182 assert(iso);
124 assert(x >= 0); 183 assert(x >= 0);
125 assert(y >= 0); 184 assert(y >= 0);
@@ -128,169 +187,279 @@ static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) {
128 return &iso->screen[y * iso->screen_width + x]; 187 return &iso->screen[y * iso->screen_width + x];
129} 188}
130 189
131static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { 190static inline Pixel screen_xy(IsoGfx* iso, int x, int y) {
132 assert(iso); 191 return *screen_xy_const_ref(iso, x, y);
133
134 const TileData* data = mempool_get_block(&iso->tiles, tile);
135 assert(data);
136
137 for (int py = 0; py < iso->tile_height; ++py) {
138 for (int px = 0; px < iso->tile_width; ++px) {
139 const Pixel colour = tile_xy(iso, data, px, py);
140 const int sx = origin.x + px;
141 const int sy = origin.y + py;
142 if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) &&
143 (sy < iso->screen_height)) {
144 const uint8_t mask = iso->tile_mask[py * iso->tile_width + px];
145 if (mask == 1) {
146 *screen_xy_mut(iso, sx, sy) = colour;
147 }
148 }
149 }
150 }
151} 192}
152 193
153static void draw(IsoGfx* iso) { 194static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) {
154 assert(iso); 195 return (Pixel*)screen_xy_const_ref(iso, x, y);
155 196}
156 const int W = iso->screen_width;
157 const int H = iso->screen_height;
158 197
159 memset(iso->screen, 0, W * H * sizeof(Pixel)); 198// -----------------------------------------------------------------------------
199// Renderer, world and tile management.
200// -----------------------------------------------------------------------------
160 201
161 const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; 202IsoGfx* isogfx_new(const IsoGfxDesc* desc) {
162 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; 203 assert(desc->screen_width > 0);
163 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; 204 assert(desc->screen_height > 0);
205 // Part of our implementation assumes even widths and heights for precision.
206 assert((desc->screen_width & 1) == 0);
207 assert((desc->screen_height & 1) == 0);
164 208
165 // TODO: Culling. 209 IsoGfx* iso = calloc(1, sizeof(IsoGfx));
166 // Ex: map the screen corners to tile space to cull. 210 if (!iso) {
167 // Ex: walk in screen space and fetch the tile. 211 return 0;
168 // The tile-centric approach might be more cache-friendly, however, since the
169 // screen-centric approach would juggle multiple tiles throughout the scan.
170 for (int ty = 0; ty < iso->world_height; ++ty) {
171 for (int tx = 0; tx < iso->world_width; ++tx) {
172 const Tile tile = world_xy(iso, tx, ty);
173 const ivec2 so =
174 ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty)));
175 draw_tile(iso, so, tile);
176 }
177 } 212 }
178}
179
180/// Creates a tile mask procedurally.
181static void make_tile_mask(IsoGfx* iso) {
182 assert(iso);
183 assert(iso->tile_mask);
184 213
185 for (int y = 0; y < iso->tile_height / 2; ++y) { 214 iso->screen_width = desc->screen_width;
186 const int mask_start = iso->tile_width / 2 - 2 * y - 1; 215 iso->screen_height = desc->screen_height;
187 const int mask_end = iso->tile_width / 2 + 2 * y + 1;
188 for (int x = 0; x < iso->tile_width; ++x) {
189 const bool masked = (mask_start <= x) && (x <= mask_end);
190 const uint8_t val = masked ? 1 : 0;
191 216
192 // Top half. 217 const int screen_size = desc->screen_width * desc->screen_height;
193 iso->tile_mask[y * iso->tile_width + x] = val;
194 218
195 // Bottom half reflects the top half. 219 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) {
196 const int y_reflected = iso->tile_height - y - 1; 220 goto cleanup;
197 iso->tile_mask[y_reflected * iso->tile_width + x] = val;
198 }
199 } 221 }
222
223 return iso;
224
225cleanup:
226 isogfx_del(&iso);
227 return 0;
200} 228}
201 229
202/// Creates a tile with a constant colour. 230/// Destroy the world and its tile set.
203static void make_tile_from_colour( 231static void destroy_world(IsoGfx* iso) {
204 const IsoGfx* iso, Pixel colour, TileData* tile) {
205 assert(iso); 232 assert(iso);
206 assert(tile); 233 if (iso->world) {
234 free(iso->world);
235 iso->world = 0;
236 }
237 mempool_del(&iso->tiles);
238 mempool_del(&iso->pixels);
239}
207 240
208 for (int y = 0; y < iso->tile_height; ++y) { 241void isogfx_del(IsoGfx** pIso) {
209 for (int x = 0; x < iso->tile_width; ++x) { 242 assert(pIso);
210 *tile_xy_mut(iso, tile, x, y) = colour; 243 IsoGfx* iso = *pIso;
244 if (iso) {
245 destroy_world(iso);
246 if (iso->screen) {
247 free(iso->screen);
248 iso->screen = 0;
211 } 249 }
250 free(iso);
251 *pIso = 0;
212 } 252 }
213} 253}
214 254
215IsoGfx* isogfx_new(const IsoGfxDesc* desc) { 255bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) {
216 assert(desc->screen_width > 0); 256 assert(iso);
217 assert(desc->screen_height > 0); 257 assert(desc);
218 assert(desc->tile_width > 0); 258 assert(desc->tile_width > 0);
219 assert(desc->tile_height > 0); 259 assert(desc->tile_height > 0);
220 // Part of our implementation assumes even widths and heights for greater 260 // Part of our implementation assumes even widths and heights for greater
221 // precision. 261 // precision.
222 assert((desc->screen_width & 1) == 0);
223 assert((desc->screen_height & 1) == 0);
224 assert((desc->tile_width & 1) == 0); 262 assert((desc->tile_width & 1) == 0);
225 assert((desc->tile_height & 1) == 0); 263 assert((desc->tile_height & 1) == 0);
226 264
227 IsoGfx* iso = calloc(1, sizeof(IsoGfx)); 265 // Handle recreation by destroying the previous world.
228 if (!iso) { 266 destroy_world(iso);
229 return 0;
230 }
231
232 iso->screen_width = desc->screen_width;
233 iso->screen_height = desc->screen_height;
234 iso->tile_width = desc->tile_width;
235 iso->tile_height = desc->tile_height;
236 iso->world_width = desc->world_width;
237 iso->world_height = desc->world_height;
238 iso->max_num_tiles =
239 desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES;
240 267
241 const int world_size = desc->world_width * desc->world_height; 268 iso->tile_width = desc->tile_width;
242 const int screen_size = desc->screen_width * desc->screen_height; 269 iso->tile_height = desc->tile_height;
243 const int tile_size = desc->tile_width * desc->tile_height; 270 iso->world_width = desc->world_width;
271 iso->world_height = desc->world_height;
244 272
273 const int world_size = desc->world_width * desc->world_height;
274 const int tile_size = desc->tile_width * desc->tile_height;
245 const int tile_size_bytes = tile_size * (int)sizeof(Pixel); 275 const int tile_size_bytes = tile_size * (int)sizeof(Pixel);
276 const int tile_pool_size =
277 desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES;
246 278
247 if (!(iso->world = calloc(world_size, sizeof(Tile)))) { 279 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
248 goto cleanup; 280 goto cleanup;
249 } 281 }
250 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { 282 if (!mempool_make_dyn(&iso->tiles, tile_pool_size, tile_size_bytes)) {
283 goto cleanup;
284 }
285
286 return true;
287
288cleanup:
289 destroy_world(iso);
290 mempool_del(&iso->tiles);
291 return false;
292}
293
294bool isogfx_load_world(IsoGfx* iso, const char* filepath) {
295 assert(iso);
296 assert(filepath);
297
298 bool success = false;
299
300 // Handle recreation by destroying the previous world.
301 destroy_world(iso);
302
303 // Load the map.
304 printf("Load tile map: %s\n", filepath);
305 Tm_Map* map = read_file(filepath);
306 if (!map) {
251 goto cleanup; 307 goto cleanup;
252 } 308 }
253 if (!(iso->tile_mask = calloc(tile_size, sizeof(uint8_t)))) { 309
310 // Allocate memory for the map and tile sets.
311 const int world_size = map->world_width * map->world_height;
312 const int base_tile_size = map->base_tile_width * map->base_tile_height;
313 const int base_tile_size_bytes = base_tile_size * (int)sizeof(Pixel);
314 // TODO: Need to get the total number of tiles from the map.
315 const int tile_pool_size = DEFAULT_MAX_NUM_TILES;
316
317 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
254 goto cleanup; 318 goto cleanup;
255 } 319 }
256 if (!mempool_make_dyn(&iso->tiles, iso->max_num_tiles, tile_size_bytes)) { 320 if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) {
321 goto cleanup;
322 }
323 if (!mempool_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) {
257 goto cleanup; 324 goto cleanup;
258 } 325 }
259 326
260 make_tile_mask(iso); 327 // Load the tile sets.
328 const Tm_Layer* layer = &map->layers[0];
329 // TODO: Handle num_layers layers.
330 for (int i = 0; i < 1; ++i) {
331 const char* ts_path = layer->tileset_path;
332
333 // Tile set path is relative to the tile map file. Make it relative to the
334 // current working directory before loading.
335 char ts_path_cwd[PATH_MAX] = {0};
336 if (!make_relative_path(MAX_PATH_LENGTH, filepath, ts_path, ts_path_cwd)) {
337 goto cleanup;
338 }
261 339
262 return iso; 340 Ts_TileSet* tileset = read_file(ts_path_cwd);
341 if (!tileset) {
342 goto cleanup;
343 };
344
345 // Load tile data.
346 const Ts_Tile* tile = &tileset->tiles[0];
347 for (uint16_t j = 0; j < tileset->num_tiles; ++j) {
348 // Tile dimensions should be a multiple of the base tile size.
349 assert((tile->width % map->base_tile_width) == 0);
350 assert((tile->height % map->base_tile_height) == 0);
351
352 const uint16_t tile_size = tile->width * tile->height;
353
354 // TODO: Add function in mempool to alloc N consecutive blocks.
355 const int num_blocks = tile_size / base_tile_size;
356 Pixel* pixels = mempool_alloc(&iso->pixels);
357 assert(pixels);
358 // This is ugly and assumes that blocks are allocated consecutively.
359 for (int b = 1; b < num_blocks; ++b) {
360 Pixel* block = mempool_alloc(&iso->pixels);
361 assert(block);
362 }
363 memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel));
263 364
264cleanup: 365 TileData* tile_data = mempool_alloc(&iso->tiles);
265 isogfx_del(&iso); 366 assert(tile_data);
266 return 0; 367 tile_data->width = tile->width;
267} 368 tile_data->height = tile->height;
369 tile_data->num_blocks = (uint16_t)num_blocks;
370 tile_data->pixels_index =
371 (uint16_t)mempool_get_block_index(&iso->pixels, pixels);
268 372
269void isogfx_del(IsoGfx** pIso) { 373 tile = ts_tileset_get_next_tile(tileset, tile);
270 assert(pIso);
271 IsoGfx* iso = *pIso;
272 if (iso) {
273 if (iso->world) {
274 free(iso->world);
275 } 374 }
276 if (iso->screen) { 375
277 free(iso->screen); 376 printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd);
377
378 free(tileset);
379 layer = tm_map_get_next_layer(map, layer);
380 }
381
382 // Load the map into the world.
383 layer = &map->layers[0];
384 // TODO: Handle num_layers layers.
385 for (int i = 0; i < 1; ++i) {
386 memcpy(iso->world, layer->tiles, world_size * sizeof(Tile));
387
388 // TODO: We need to handle 'firsgid' in TMX files.
389 for (int j = 0; j < world_size; ++j) {
390 iso->world[j] -= 1;
278 } 391 }
279 if (iso->tile_mask) { 392
280 free(iso->tile_mask); 393 layer = tm_map_get_next_layer(map, layer);
394 }
395
396 iso->world_width = map->world_width;
397 iso->world_height = map->world_height;
398 iso->tile_width = map->base_tile_width;
399 iso->tile_height = map->base_tile_height;
400
401 success = true;
402
403cleanup:
404 if (map) {
405 free(map);
406 }
407 if (!success) {
408 destroy_world(iso);
409 }
410 return success;
411}
412
413int isogfx_world_width(const IsoGfx* iso) {
414 assert(iso);
415 return iso->world_width;
416}
417
418int isogfx_world_height(const IsoGfx* iso) {
419 assert(iso);
420 return iso->world_height;
421}
422
423/// Create a tile mask procedurally.
424static void make_tile_from_colour(
425 const IsoGfx* iso, Pixel colour, TileData* tile) {
426 assert(iso);
427 assert(tile);
428
429 const int width = tile->width;
430 const int height = tile->height;
431 const int r = width / height;
432
433 for (int y = 0; y < height / 2; ++y) {
434 const int mask_start = width / 2 - r * y - 1;
435 const int mask_end = width / 2 + r * y + 1;
436 for (int x = 0; x < width; ++x) {
437 const bool mask = (mask_start <= x) && (x <= mask_end);
438 const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0};
439
440 // Top half.
441 *tile_xy_mut(iso, tile, x, y) = val;
442
443 // Bottom half reflects the top half.
444 const int y_reflected = height - y - 1;
445 *tile_xy_mut(iso, tile, x, y_reflected) = val;
281 } 446 }
282 mempool_del(&iso->tiles);
283 free(iso);
284 } 447 }
285} 448}
286 449
287Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { 450Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
288 assert(iso); 451 assert(iso);
289 assert(desc); 452 assert(desc);
453 // Client must create world before creating tiles.
454 assert(iso->tile_width > 0);
455 assert(iso->tile_height > 0);
290 456
291 TileData* tile = mempool_alloc(&iso->tiles); 457 TileData* tile = mempool_alloc(&iso->tiles);
292 assert(tile); // TODO: Make this a hard assert. 458 assert(tile); // TODO: Make this a hard assert.
293 459
460 tile->width = desc->width;
461 tile->height = desc->height;
462
294 switch (desc->type) { 463 switch (desc->type) {
295 case TileFromColour: 464 case TileFromColour:
296 make_tile_from_colour(iso, desc->colour, tile); 465 make_tile_from_colour(iso, desc->colour, tile);
@@ -311,6 +480,88 @@ void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) {
311 *world_xy_mut(iso, x, y) = tile; 480 *world_xy_mut(iso, x, y) = tile;
312} 481}
313 482
483void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) {
484 assert(iso);
485 for (int y = y0; y < y1; ++y) {
486 for (int x = x0; x < x1; ++x) {
487 isogfx_set_tile(iso, x, y, tile);
488 }
489 }
490}
491
492// -----------------------------------------------------------------------------
493// Rendering and picking.
494// -----------------------------------------------------------------------------
495
496static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) {
497 assert(iso);
498
499 const TileData* tile_data = mempool_get_block(&iso->tiles, tile);
500 assert(tile_data);
501
502 // Tile can exceed screen bounds, so we must clip it.
503#define max(a, b) (a > b ? a : b)
504 const int py_offset = max(0, (int)tile_data->height - origin.y);
505 origin.y = max(0, origin.y - (int)tile_data->height);
506
507 // Clip along Y and X as we draw.
508 for (int py = py_offset;
509 (py < tile_data->height) && (origin.y + py < iso->screen_height); ++py) {
510 const int sy = origin.y + py - py_offset;
511 for (int px = 0;
512 (px < tile_data->width) && (origin.x + px < iso->screen_width); ++px) {
513 const Pixel colour = tile_xy(iso, tile_data, px, py);
514 if (colour.a > 0) {
515 const int sx = origin.x + px;
516 *screen_xy_mut(iso, sx, sy) = colour;
517 }
518 }
519 }
520
521 // for (int py = 0; py < tile_data->height; ++py) {
522 // for (int px = 0; px < tile_data->width; ++px) {
523 // const Pixel colour = tile_xy(iso, tile_data, px, py);
524 // if (colour.a > 0) {
525 // const int sx = origin.x + px;
526 // const int sy = origin.y + py;
527 // if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) &&
528 // (sy < iso->screen_height)) {
529 // *screen_xy_mut(iso, sx, sy) = colour;
530 // }
531 // }
532 // }
533 // }
534}
535
536static void draw(IsoGfx* iso) {
537 assert(iso);
538
539 const int W = iso->screen_width;
540 const int H = iso->screen_height;
541
542 memset(iso->screen, 0, W * H * sizeof(Pixel));
543
544 // const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0};
545 const ivec2 o = {
546 (iso->screen_width / 2) - (iso->tile_width / 2), iso->tile_height};
547 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
548 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
549
550 // TODO: Culling.
551 // Ex: map the screen corners to tile space to cull.
552 // Ex: walk in screen space and fetch the tile.
553 // The tile-centric approach might be more cache-friendly since the
554 // screen-centric approach would juggle multiple tiles throughout the scan.
555 for (int ty = 0; ty < iso->world_height; ++ty) {
556 for (int tx = 0; tx < iso->world_width; ++tx) {
557 const Tile tile = world_xy(iso, tx, ty);
558 const ivec2 so =
559 ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty)));
560 draw_tile(iso, so, tile);
561 }
562 }
563}
564
314void isogfx_pick_tile( 565void isogfx_pick_tile(
315 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { 566 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) {
316 assert(iso); 567 assert(iso);
@@ -356,13 +607,3 @@ const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) {
356 assert(iso); 607 assert(iso);
357 return iso->screen; 608 return iso->screen;
358} 609}
359
360int isogfx_world_width(const IsoGfx* iso) {
361 assert(iso);
362 return iso->world_width;
363}
364
365int isogfx_world_height(const IsoGfx* iso) {
366 assert(iso);
367 return iso->world_height;
368}