summaryrefslogtreecommitdiff
path: root/gfx-iso/src/isogfx.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-06-24 18:46:33 -0700
committer3gg <3gg@shellblade.net>2023-06-24 18:46:33 -0700
commit0831d5bce79008bfa6404f8e8116ae8290442fde (patch)
treee488c719e16b34b60126837a90a44d1c3dd552ee /gfx-iso/src/isogfx.c
parentcf886f4fa406ddd48f30c00ad3c77f9dc134af3a (diff)
Isometric Renderer initial commit.
Diffstat (limited to 'gfx-iso/src/isogfx.c')
-rw-r--r--gfx-iso/src/isogfx.c361
1 files changed, 361 insertions, 0 deletions
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c
new file mode 100644
index 0000000..27981f9
--- /dev/null
+++ b/gfx-iso/src/isogfx.c
@@ -0,0 +1,361 @@
1#include <isogfx/isogfx.h>
2
3#include <mempool.h>
4
5#include <assert.h>
6#include <stdbool.h>
7#include <stdint.h>
8#include <stdlib.h>
9#include <string.h>
10
11/// Maximum number of tiles unless the user chooses a non-zero value.
12#define DEFAULT_MAX_NUM_TILES 1024
13
14typedef struct TileData {
15 Pixel pixels[1]; // Dynamically allocated.
16} TileData;
17
18DEF_MEMPOOL_DYN(TilePool, TileData)
19
20typedef struct IsoGfx {
21 Tile* world;
22 Pixel* screen;
23 uint8_t* tile_mask;
24 TilePool tiles;
25 int screen_width;
26 int screen_height;
27 int tile_width;
28 int tile_height;
29 int world_width;
30 int world_height;
31 int max_num_tiles;
32} IsoGfx;
33
34typedef struct ivec2 {
35 int x, y;
36} ivec2;
37
38typedef struct vec2 {
39 double x, y;
40} vec2;
41
42static inline ivec2 ivec2_add(ivec2 a, ivec2 b) {
43 return (ivec2){.x = a.x + b.x, .y = a.y + b.y};
44}
45
46static inline ivec2 ivec2_scale(ivec2 a, int s) {
47 return (ivec2){.x = a.x * s, .y = a.y * s};
48}
49
50static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) {
51 return (ivec2){
52 .x = (iso.x - iso.y) * (s / 2) + (w / 2), .y = (iso.x + iso.y) * (t / 2)};
53}
54
55static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
56 const double one_over_s = 1. / (double)s;
57 const double one_over_t = 1. / (double)t;
58 const double x = cart.x - (double)(w / 2);
59
60 return (vec2){
61 .x = (int)(one_over_s * x + one_over_t * cart.y),
62 .y = (int)(-one_over_s * x + one_over_t * cart.y)};
63}
64
65Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) {
66 assert(iso);
67 assert(tile);
68 assert(tile->pixels);
69 assert(x >= 0);
70 assert(y >= 0);
71 assert(x < iso->tile_width);
72 assert(y < iso->tile_height);
73 return &tile->pixels[y * iso->tile_width + x];
74}
75
76Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) {
77 assert(iso);
78 assert(tile);
79 assert(tile->pixels);
80 assert(x >= 0);
81 assert(y >= 0);
82 assert(x < iso->tile_width);
83 assert(y < iso->tile_height);
84 return tile->pixels[y * iso->tile_width + x];
85}
86
87static inline Tile world_xy(IsoGfx* iso, int x, int y) {
88 assert(iso);
89 assert(x >= 0);
90 assert(y >= 0);
91 assert(x < iso->world_width);
92 assert(y < iso->world_height);
93 return iso->world[y * iso->world_width + x];
94}
95
96static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
97 assert(iso);
98 assert(x >= 0);
99 assert(y >= 0);
100 assert(x < iso->world_width);
101 assert(y < iso->world_height);
102 return &iso->world[y * iso->world_width + x];
103}
104
105static inline Pixel screen_xy(IsoGfx* iso, int x, int y) {
106 assert(iso);
107 assert(x >= 0);
108 assert(y >= 0);
109 assert(x < iso->screen_width);
110 assert(y < iso->screen_height);
111 return iso->screen[y * iso->screen_width + x];
112}
113
114static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) {
115 assert(iso);
116 assert(x >= 0);
117 assert(y >= 0);
118 assert(x < iso->screen_width);
119 assert(y < iso->screen_height);
120 return &iso->screen[y * iso->screen_width + x];
121}
122
123static void draw_tile(IsoGfx* iso, ivec2 so, Tile tile) {
124 assert(iso);
125
126 const TileData* data = mempool_get_block(&iso->tiles, tile);
127 assert(data);
128
129 for (int py = 0; py < iso->tile_height; ++py) {
130 for (int px = 0; px < iso->tile_width; ++px) {
131 const Pixel colour = tile_xy(iso, data, px, py);
132 const int sx = so.x + px;
133 const int sy = so.y + py;
134 if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) &&
135 (sy < iso->screen_height)) {
136 const uint8_t mask = iso->tile_mask[py * iso->tile_width + px];
137 if (mask == 1) {
138 *screen_xy_mut(iso, sx, sy) = colour;
139 }
140 }
141 }
142 }
143}
144
145static void draw(IsoGfx* iso) {
146 assert(iso);
147
148 const int W = iso->screen_width;
149 const int H = iso->screen_height;
150
151 memset(iso->screen, 0, W * H * sizeof(Pixel));
152
153 const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0};
154 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
155 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
156
157 // TODO: Since the world will generally be larger than the screen, it
158 // would be best to walk in screen space and fetch the tile.
159 // The tile-centric approach might be more cache-friendly, however, since the
160 // screen-centric approach would juggle multiple tiles throughout the scan.
161 for (int ty = 0; ty < iso->world_height; ++ty) {
162 for (int tx = 0; tx < iso->world_width; ++tx) {
163 const Tile tile = world_xy(iso, tx, ty);
164 const ivec2 so =
165 ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty)));
166 draw_tile(iso, so, tile);
167 }
168 }
169}
170
171/// Creates a tile mask procedurally.
172static void make_tile_mask(IsoGfx* iso) {
173 assert(iso);
174 assert(iso->tile_mask);
175
176 for (int y = 0; y < iso->tile_height / 2; ++y) {
177 const int mask_start = iso->tile_width / 2 - 2 * y - 1;
178 const int mask_end = iso->tile_width / 2 + 2 * y + 1;
179 for (int x = 0; x < iso->tile_width; ++x) {
180 const bool masked = (mask_start <= x) && (x <= mask_end);
181 const uint8_t val = masked ? 1 : 0;
182
183 // Top half.
184 iso->tile_mask[y * iso->tile_width + x] = val;
185
186 // Bottom half reflects the top half.
187 const int y_reflected = iso->tile_height - y - 1;
188 iso->tile_mask[y_reflected * iso->tile_width + x] = val;
189 }
190 }
191}
192
193/// Creates a tile with a constant colour.
194static void make_tile_from_colour(
195 const IsoGfx* iso, Pixel colour, TileData* tile) {
196 assert(iso);
197 assert(tile);
198
199 for (int y = 0; y < iso->tile_height; ++y) {
200 for (int x = 0; x < iso->tile_width; ++x) {
201 *tile_xy_mut(iso, tile, x, y) = colour;
202 }
203 }
204}
205
206IsoGfx* isogfx_new(const IsoGfxDesc* desc) {
207 assert(desc->screen_width > 0);
208 assert(desc->screen_height > 0);
209 assert(desc->tile_width > 0);
210 assert(desc->tile_height > 0);
211 // Part of our implementation assumes even widths and heights for greater
212 // precision.
213 assert((desc->screen_width & 1) == 0);
214 assert((desc->screen_height & 1) == 0);
215 assert((desc->tile_width & 1) == 0);
216 assert((desc->tile_height & 1) == 0);
217
218 IsoGfx* iso = calloc(1, sizeof(IsoGfx));
219 if (!iso) {
220 return 0;
221 }
222
223 iso->screen_width = desc->screen_width;
224 iso->screen_height = desc->screen_height;
225 iso->tile_width = desc->tile_width;
226 iso->tile_height = desc->tile_height;
227 iso->world_width = desc->world_width;
228 iso->world_height = desc->world_height;
229 iso->max_num_tiles =
230 desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES;
231
232 const int world_size = desc->world_width * desc->world_height;
233 const int screen_size = desc->screen_width * desc->screen_height;
234 const int tile_size = desc->tile_width * desc->tile_height;
235
236 const int tile_size_bytes = tile_size * (int)sizeof(Pixel);
237
238 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
239 goto cleanup;
240 }
241 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) {
242 goto cleanup;
243 }
244 if (!(iso->tile_mask = calloc(tile_size, sizeof(uint8_t)))) {
245 goto cleanup;
246 }
247 if (!mempool_make_dyn(&iso->tiles, iso->max_num_tiles, tile_size_bytes)) {
248 goto cleanup;
249 }
250
251 make_tile_mask(iso);
252
253 return iso;
254
255cleanup:
256 isogfx_del(&iso);
257 return 0;
258}
259
260void isogfx_del(IsoGfx** pIso) {
261 assert(pIso);
262 IsoGfx* iso = *pIso;
263 if (iso) {
264 if (iso->world) {
265 free(iso->world);
266 }
267 if (iso->screen) {
268 free(iso->screen);
269 }
270 if (iso->tile_mask) {
271 free(iso->tile_mask);
272 }
273 mempool_del(&iso->tiles);
274 free(iso);
275 }
276}
277
278Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
279 assert(iso);
280 assert(desc);
281
282 TileData* tile = mempool_alloc(&iso->tiles);
283 assert(tile); // TODO: Make this a hard assert.
284
285 switch (desc->type) {
286 case TileFromColour:
287 make_tile_from_colour(iso, desc->colour, tile);
288 break;
289 case TileFromFile:
290 assert(false); // TODO
291 break;
292 case TileFromMemory:
293 assert(false); // TODO
294 break;
295 }
296
297 return (Tile)mempool_get_block_index(&iso->tiles, tile);
298}
299
300void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) {
301 assert(iso);
302 *world_xy_mut(iso, x, y) = tile;
303}
304
305void isogfx_pick_tile(
306 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) {
307 assert(iso);
308 assert(xiso);
309 assert(yiso);
310
311 const vec2 xy_iso = cart2iso(
312 (vec2){.x = xcart, .y = ycart}, iso->tile_width, iso->tile_height,
313 iso->screen_width);
314
315 const int x = (int)xy_iso.x;
316 const int y = (int)xy_iso.y;
317
318 if ((0 <= x) && (x < iso->world_width) && (0 <= y) &&
319 (y < iso->world_height)) {
320 *xiso = x;
321 *yiso = y;
322 } else {
323 *xiso = -1;
324 }
325}
326
327void isogfx_render(IsoGfx* iso) {
328 assert(iso);
329 draw(iso);
330}
331
332void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) {
333 assert(iso);
334 assert(x >= 0);
335 assert(y >= 0);
336 assert(x < iso->world_width);
337 assert(y < iso->world_height);
338
339 const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0};
340 const ivec2 vx = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
341 const ivec2 vy = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
342 const ivec2 so =
343 ivec2_add(o, ivec2_add(ivec2_scale(vx, x), ivec2_scale(vy, y)));
344
345 draw_tile(iso, so, tile);
346}
347
348const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) {
349 assert(iso);
350 return iso->screen;
351}
352
353int isogfx_world_width(const IsoGfx* iso) {
354 assert(iso);
355 return iso->world_width;
356}
357
358int isogfx_world_height(const IsoGfx* iso) {
359 assert(iso);
360 return iso->world_height;
361}