diff options
author | 3gg <3gg@shellblade.net> | 2023-07-08 14:37:29 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2023-07-08 14:37:29 -0700 |
commit | 21a0d0c1c424f7db90c3282aad4bf6ad4ef809b7 (patch) | |
tree | a6ae8a8cb4108cd33713178e67d3b482fc1fd5ee /gfx-iso/src | |
parent | 303f5dc58dd8e8266df3c62fc84d9799db8047b9 (diff) |
Load tile maps and tile sets from files.
Diffstat (limited to 'gfx-iso/src')
-rw-r--r-- | gfx-iso/src/isogfx.c | 561 |
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 | |||
25 | typedef 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 | |||
31 | typedef 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 | |||
38 | typedef 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 | |||
45 | typedef 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 | |||
54 | static 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 | |||
63 | static 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 | |||
14 | typedef struct TileData { | 77 | typedef 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 | ||
18 | DEF_MEMPOOL_DYN(TilePool, TileData) | 84 | DEF_MEMPOOL_DYN(TilePool, TileData) |
85 | DEF_MEMPOOL_DYN(PixelPool, Pixel) | ||
19 | 86 | ||
20 | typedef struct IsoGfx { | 87 | typedef 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 | |||
34 | typedef struct ivec2 { | 104 | typedef 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 | ||
73 | Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { | 143 | static 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 | ||
84 | Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { | 155 | static 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 | ||
95 | static inline Tile world_xy(IsoGfx* iso, int x, int y) { | 159 | static 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 | ||
104 | static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { | 163 | static 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 | ||
113 | static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { | 172 | static 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 | ||
122 | static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { | 176 | static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { |
177 | return (Tile*)world_xy_const_ref(iso, x, y); | ||
178 | } | ||
179 | |||
180 | static 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 | ||
131 | static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { | 190 | static 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 | ||
153 | static void draw(IsoGfx* iso) { | 194 | static 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}; | 202 | IsoGfx* 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. | ||
181 | static 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 | |||
225 | cleanup: | ||
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. |
203 | static void make_tile_from_colour( | 231 | static 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) { | 241 | void 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 | ||
215 | IsoGfx* isogfx_new(const IsoGfxDesc* desc) { | 255 | bool 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 | |||
288 | cleanup: | ||
289 | destroy_world(iso); | ||
290 | mempool_del(&iso->tiles); | ||
291 | return false; | ||
292 | } | ||
293 | |||
294 | bool 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 | ||
264 | cleanup: | 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 | ||
269 | void 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 | |||
403 | cleanup: | ||
404 | if (map) { | ||
405 | free(map); | ||
406 | } | ||
407 | if (!success) { | ||
408 | destroy_world(iso); | ||
409 | } | ||
410 | return success; | ||
411 | } | ||
412 | |||
413 | int isogfx_world_width(const IsoGfx* iso) { | ||
414 | assert(iso); | ||
415 | return iso->world_width; | ||
416 | } | ||
417 | |||
418 | int isogfx_world_height(const IsoGfx* iso) { | ||
419 | assert(iso); | ||
420 | return iso->world_height; | ||
421 | } | ||
422 | |||
423 | /// Create a tile mask procedurally. | ||
424 | static 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 | ||
287 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { | 450 | Tile 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 | ||
483 | void 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 | |||
496 | static 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 | |||
536 | static 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 | |||
314 | void isogfx_pick_tile( | 565 | void 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 | |||
360 | int isogfx_world_width(const IsoGfx* iso) { | ||
361 | assert(iso); | ||
362 | return iso->world_width; | ||
363 | } | ||
364 | |||
365 | int isogfx_world_height(const IsoGfx* iso) { | ||
366 | assert(iso); | ||
367 | return iso->world_height; | ||
368 | } | ||