diff options
Diffstat (limited to 'src/gfx2d.c')
-rw-r--r-- | src/gfx2d.c | 573 |
1 files changed, 364 insertions, 209 deletions
diff --git a/src/gfx2d.c b/src/gfx2d.c index da265b0..8d78c2b 100644 --- a/src/gfx2d.c +++ b/src/gfx2d.c | |||
@@ -20,6 +20,12 @@ | |||
20 | /// Time between animation updates. | 20 | /// Time between animation updates. |
21 | #define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS) | 21 | #define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS) |
22 | 22 | ||
23 | /// Take the maximum of two values. | ||
24 | #define max(a, b) ((a) > (b) ? (a) : (b)) | ||
25 | |||
26 | /// Take the minimum of two values. | ||
27 | #define min(a, b) ((a) < (b) ? (a) : (b)) | ||
28 | |||
23 | typedef struct ivec2 { | 29 | typedef struct ivec2 { |
24 | int x, y; | 30 | int x, y; |
25 | } ivec2; | 31 | } ivec2; |
@@ -32,11 +38,11 @@ typedef struct vec2 { | |||
32 | // Renderer state. | 38 | // Renderer state. |
33 | // ----------------------------------------------------------------------------- | 39 | // ----------------------------------------------------------------------------- |
34 | 40 | ||
35 | typedef struct CoordSystem { | 41 | typedef struct IsoCoordSystem { |
36 | ivec2 o; // Origin. | 42 | ivec2 o; // Origin. |
37 | ivec2 x; | 43 | ivec2 x; |
38 | ivec2 y; | 44 | ivec2 y; |
39 | } CoordSystem; | 45 | } IsoCoordSystem; |
40 | 46 | ||
41 | typedef struct Screen { | 47 | typedef struct Screen { |
42 | int width; | 48 | int width; |
@@ -52,9 +58,9 @@ typedef struct SpriteInstance { | |||
52 | int frame; // Current frame of animation. | 58 | int frame; // Current frame of animation. |
53 | } SpriteInstance; | 59 | } SpriteInstance; |
54 | 60 | ||
55 | typedef struct IsoGfx { | 61 | typedef struct Gfx2d { |
56 | Screen screen; | 62 | Screen screen; |
57 | CoordSystem iso_space; | 63 | IsoCoordSystem iso_space; |
58 | ivec2 camera; | 64 | ivec2 camera; |
59 | double last_animation_time; | 65 | double last_animation_time; |
60 | Tile next_tile; // For procedurally-generated tiles. | 66 | Tile next_tile; // For procedurally-generated tiles. |
@@ -63,7 +69,7 @@ typedef struct IsoGfx { | |||
63 | SpriteInstance* head_sprite; // Head of sprites list. | 69 | SpriteInstance* head_sprite; // Head of sprites list. |
64 | memstack stack; | 70 | memstack stack; |
65 | size_t watermark; | 71 | size_t watermark; |
66 | } IsoGfx; | 72 | } Gfx2d; |
67 | 73 | ||
68 | // ----------------------------------------------------------------------------- | 74 | // ----------------------------------------------------------------------------- |
69 | // Math and world / tile / screen access. | 75 | // Math and world / tile / screen access. |
@@ -79,17 +85,58 @@ static inline ivec2 ivec2_scale(ivec2 a, int s) { | |||
79 | 85 | ||
80 | static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; } | 86 | static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; } |
81 | 87 | ||
82 | static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) { | ||
83 | return (ivec2){.x = (iso.x - iso.y) * (s / 2) + (w / 2), | ||
84 | .y = (iso.x + iso.y) * (t / 2)}; | ||
85 | } | ||
86 | |||
87 | static inline vec2 vec2_add(vec2 a, vec2 b) { | 88 | static inline vec2 vec2_add(vec2 a, vec2 b) { |
88 | return (vec2){.x = a.x + b.x, .y = a.y + b.y}; | 89 | return (vec2){.x = a.x + b.x, .y = a.y + b.y}; |
89 | } | 90 | } |
90 | 91 | ||
91 | static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } | 92 | static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } |
92 | 93 | ||
94 | /// Map map coordinates to screen coordinates, both Cartesian. | ||
95 | static ivec2 map2screen( | ||
96 | ivec2 camera, int tile_width, int tile_height, int map_x, int map_y) { | ||
97 | return ivec2_add( | ||
98 | ivec2_neg(camera), | ||
99 | (ivec2){.x = map_x * tile_width, .y = map_y * tile_height}); | ||
100 | } | ||
101 | |||
102 | // Not actually used because we pre-compute the two axis vectors instead. | ||
103 | // See make_iso_coord_system() and the other definition of iso2cart() below. | ||
104 | // static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) { | ||
105 | // return (ivec2){.x = (iso.x - iso.y) * (s / 2) + (w / 2), | ||
106 | // .y = (iso.x + iso.y) * (t / 2)}; | ||
107 | // } | ||
108 | |||
109 | /// Create the basis for the isometric coordinate system with origin and vectors | ||
110 | /// expressed in the Cartesian system. | ||
111 | static IsoCoordSystem make_iso_coord_system( | ||
112 | const Tm_Map* const map, const Screen* const screen) { | ||
113 | assert(map); | ||
114 | assert(screen); | ||
115 | const ivec2 o = {screen->width / 2, 0}; | ||
116 | const ivec2 x = { | ||
117 | .x = map->base_tile_width / 2, .y = map->base_tile_height / 2}; | ||
118 | const ivec2 y = { | ||
119 | .x = -map->base_tile_width / 2, .y = map->base_tile_height / 2}; | ||
120 | return (IsoCoordSystem){o, x, y}; | ||
121 | } | ||
122 | |||
123 | /// Map isometric coordinates to Cartesian coordinates. | ||
124 | /// | ||
125 | /// For a tile, this gets the screen position of the top diamond-corner of the | ||
126 | /// tile. | ||
127 | /// | ||
128 | /// Takes the camera displacement into account. | ||
129 | static ivec2 iso2cart( | ||
130 | const IsoCoordSystem iso_space, ivec2 camera, int iso_x, int iso_y) { | ||
131 | const ivec2 vx_offset = ivec2_scale(iso_space.x, iso_x); | ||
132 | const ivec2 vy_offset = ivec2_scale(iso_space.y, iso_y); | ||
133 | const ivec2 origin_world_space = | ||
134 | ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset)); | ||
135 | const ivec2 origin_view_space = | ||
136 | ivec2_add(origin_world_space, ivec2_neg(camera)); | ||
137 | return origin_view_space; | ||
138 | } | ||
139 | |||
93 | // Method 1. | 140 | // Method 1. |
94 | // static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { | 141 | // static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { |
95 | // const double x = cart.x - (double)(w / 2); | 142 | // const double x = cart.x - (double)(w / 2); |
@@ -103,8 +150,8 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { | |||
103 | const double one_over_s = 1. / (double)s; | 150 | const double one_over_s = 1. / (double)s; |
104 | const double one_over_t = 1. / (double)t; | 151 | const double one_over_t = 1. / (double)t; |
105 | const double x = cart.x - (double)(w / 2); | 152 | const double x = cart.x - (double)(w / 2); |
106 | return (vec2){.x = (one_over_s * x + one_over_t * cart.y), | 153 | return (vec2){.x = ((one_over_s * x) + (one_over_t * cart.y)), |
107 | .y = (-one_over_s * x + one_over_t * cart.y)}; | 154 | .y = ((-one_over_s * x) + (one_over_t * cart.y))}; |
108 | } | 155 | } |
109 | 156 | ||
110 | static inline const Pixel* screen_xy_const_ref( | 157 | static inline const Pixel* screen_xy_const_ref( |
@@ -114,10 +161,10 @@ static inline const Pixel* screen_xy_const_ref( | |||
114 | assert(y >= 0); | 161 | assert(y >= 0); |
115 | assert(x < screen->width); | 162 | assert(x < screen->width); |
116 | assert(y < screen->height); | 163 | assert(y < screen->height); |
117 | return &screen->pixels[y * screen->width + x]; | 164 | return &screen->pixels[(y * screen->width) + x]; |
118 | } | 165 | } |
119 | 166 | ||
120 | static inline Pixel screen_xy(Screen* screen, int x, int y) { | 167 | static inline Pixel screen_xy(const Screen* screen, int x, int y) { |
121 | return *screen_xy_const_ref(screen, x, y); | 168 | return *screen_xy_const_ref(screen, x, y); |
122 | } | 169 | } |
123 | 170 | ||
@@ -125,81 +172,67 @@ static inline Pixel* screen_xy_mut(Screen* screen, int x, int y) { | |||
125 | return (Pixel*)screen_xy_const_ref(screen, x, y); | 172 | return (Pixel*)screen_xy_const_ref(screen, x, y); |
126 | } | 173 | } |
127 | 174 | ||
128 | /// Create the basis for the isometric coordinate system with origin and vectors | ||
129 | /// expressed in the Cartesian system. | ||
130 | static CoordSystem make_iso_coord_system( | ||
131 | const Tm_Map* const map, const Screen* const screen) { | ||
132 | assert(map); | ||
133 | assert(screen); | ||
134 | const ivec2 o = {screen->width / 2, 0}; | ||
135 | const ivec2 x = { | ||
136 | .x = map->base_tile_width / 2, .y = map->base_tile_height / 2}; | ||
137 | const ivec2 y = { | ||
138 | .x = -map->base_tile_width / 2, .y = map->base_tile_height / 2}; | ||
139 | return (CoordSystem){o, x, y}; | ||
140 | } | ||
141 | |||
142 | // ----------------------------------------------------------------------------- | 175 | // ----------------------------------------------------------------------------- |
143 | // Renderer, world and tile management. | 176 | // Renderer, world and tile management. |
144 | // ----------------------------------------------------------------------------- | 177 | // ----------------------------------------------------------------------------- |
145 | 178 | ||
146 | IsoGfx* isogfx_new(const IsoGfxDesc* desc) { | 179 | Gfx2d* gfx2d_new(const Gfx2dDesc* desc) { |
147 | assert(desc->screen_width > 0); | 180 | assert(desc->screen_width > 0); |
148 | assert(desc->screen_height > 0); | 181 | assert(desc->screen_height > 0); |
149 | // Part of our implementation assumes even widths and heights for precision. | 182 | // Part of our implementation assumes even widths and heights for precision. |
150 | assert((desc->screen_width & 1) == 0); | 183 | assert((desc->screen_width & 1) == 0); |
151 | assert((desc->screen_height & 1) == 0); | 184 | assert((desc->screen_height & 1) == 0); |
152 | 185 | ||
153 | IsoGfx tmp = {0}; | 186 | Gfx2d tmp = {0}; |
154 | if (!memstack_make(&tmp.stack, desc->memory_size, desc->memory)) { | 187 | if (!memstack_make(&tmp.stack, desc->memory_size, desc->memory)) { |
155 | goto cleanup; | 188 | goto cleanup; |
156 | } | 189 | } |
157 | IsoGfx* iso = | 190 | Gfx2d* gfx = |
158 | memstack_alloc_aligned(&tmp.stack, sizeof(IsoGfx), alignof(IsoGfx)); | 191 | memstack_alloc_aligned(&tmp.stack, sizeof(Gfx2d), alignof(Gfx2d)); |
159 | *iso = tmp; | 192 | *gfx = tmp; |
160 | 193 | ||
161 | const size_t screen_size_bytes = | 194 | const size_t screen_size_bytes = |
162 | desc->screen_width * desc->screen_height * sizeof(Pixel); | 195 | desc->screen_width * desc->screen_height * sizeof(Pixel); |
163 | Pixel* screen = | 196 | Pixel* screen = |
164 | memstack_alloc_aligned(&iso->stack, screen_size_bytes, alignof(Pixel)); | 197 | memstack_alloc_aligned(&gfx->stack, screen_size_bytes, alignof(Pixel)); |
165 | 198 | ||
166 | iso->screen = (Screen){.width = desc->screen_width, | 199 | gfx->screen = (Screen){.width = desc->screen_width, |
167 | .height = desc->screen_height, | 200 | .height = desc->screen_height, |
168 | .pixels = screen}; | 201 | .pixels = screen}; |
169 | 202 | ||
170 | iso->last_animation_time = 0.0; | 203 | gfx->last_animation_time = 0.0; |
171 | iso->watermark = memstack_watermark(&iso->stack); | 204 | gfx->watermark = memstack_watermark(&gfx->stack); |
172 | 205 | ||
173 | return iso; | 206 | return gfx; |
174 | 207 | ||
175 | cleanup: | 208 | cleanup: |
176 | isogfx_del(&iso); | 209 | gfx2d_del(&gfx); |
177 | return nullptr; | 210 | return nullptr; |
178 | } | 211 | } |
179 | 212 | ||
180 | void isogfx_clear(IsoGfx* iso) { | 213 | void gfx2d_clear(Gfx2d* gfx) { |
181 | assert(iso); | 214 | assert(gfx); |
182 | iso->last_animation_time = 0.0; | 215 | gfx->last_animation_time = 0.0; |
183 | iso->next_tile = 0; | 216 | gfx->next_tile = 0; |
184 | iso->map = nullptr; | 217 | gfx->map = nullptr; |
185 | iso->tileset = nullptr; | 218 | gfx->tileset = nullptr; |
186 | iso->head_sprite = nullptr; | 219 | gfx->head_sprite = nullptr; |
187 | // The base of the stack contains the IsoGfx and the screen buffer. Make sure | 220 | // The base of the stack contains the Gfx2d and the screen buffer. Make sure |
188 | // we don't clear them. | 221 | // we don't clear them. |
189 | memstack_set_watermark(&iso->stack, iso->watermark); | 222 | memstack_set_watermark(&gfx->stack, gfx->watermark); |
190 | } | 223 | } |
191 | 224 | ||
192 | void isogfx_del(IsoGfx** ppIso) { | 225 | void gfx2d_del(Gfx2d** ppGfx) { |
193 | assert(ppIso); | 226 | assert(ppGfx); |
194 | IsoGfx* iso = *ppIso; | 227 | Gfx2d* gfx = *ppGfx; |
195 | if (iso) { | 228 | if (gfx) { |
196 | memstack_del(&iso->stack); | 229 | memstack_del(&gfx->stack); |
197 | *ppIso = nullptr; | 230 | *ppGfx = nullptr; |
198 | } | 231 | } |
199 | } | 232 | } |
200 | 233 | ||
201 | void isogfx_make_map(IsoGfx* iso, const MapDesc* desc) { | 234 | void gfx2d_make_map(Gfx2d* gfx, const MapDesc* desc) { |
202 | assert(iso); | 235 | assert(gfx); |
203 | assert(desc); | 236 | assert(desc); |
204 | assert(desc->tile_width > 0); | 237 | assert(desc->tile_width > 0); |
205 | assert(desc->tile_height > 0); | 238 | assert(desc->tile_height > 0); |
@@ -214,7 +247,7 @@ void isogfx_make_map(IsoGfx* iso, const MapDesc* desc) { | |||
214 | assert(desc->num_tiles > 0); | 247 | assert(desc->num_tiles > 0); |
215 | 248 | ||
216 | // Handle recreation by destroying the previous world and sprites. | 249 | // Handle recreation by destroying the previous world and sprites. |
217 | isogfx_clear(iso); | 250 | gfx2d_clear(gfx); |
218 | 251 | ||
219 | const int world_size = desc->world_width * desc->world_height; | 252 | const int world_size = desc->world_width * desc->world_height; |
220 | const size_t map_size_bytes = sizeof(Tm_Map) + (world_size * sizeof(Tile)); | 253 | const size_t map_size_bytes = sizeof(Tm_Map) + (world_size * sizeof(Tile)); |
@@ -229,43 +262,45 @@ void isogfx_make_map(IsoGfx* iso, const MapDesc* desc) { | |||
229 | (desc->num_tiles * sizeof(Ts_Tile)) + | 262 | (desc->num_tiles * sizeof(Ts_Tile)) + |
230 | tile_data_size_bytes; | 263 | tile_data_size_bytes; |
231 | 264 | ||
232 | iso->map = memstack_alloc_aligned(&iso->stack, map_size_bytes, 4); | 265 | gfx->map = memstack_alloc_aligned(&gfx->stack, map_size_bytes, 4); |
233 | *iso->map = (Tm_Map){ | 266 | *gfx->map = (Tm_Map){ |
234 | .world_width = desc->world_width, | 267 | .world_width = desc->world_width, |
235 | .world_height = desc->world_height, | 268 | .world_height = desc->world_height, |
236 | .base_tile_width = desc->tile_width, | 269 | .base_tile_width = desc->tile_width, |
237 | .base_tile_height = desc->tile_height, | 270 | .base_tile_height = desc->tile_height, |
238 | .num_layers = 1, | 271 | .num_layers = 1, |
272 | .flags = | ||
273 | (desc->orientation == MapOrthogonal) ? Tm_Orthogonal : Tm_Isometric, | ||
239 | }; | 274 | }; |
240 | 275 | ||
241 | iso->tileset = memstack_alloc_aligned(&iso->stack, tileset_size_bytes, 4); | 276 | gfx->tileset = memstack_alloc_aligned(&gfx->stack, tileset_size_bytes, 4); |
242 | *iso->tileset = (Ts_TileSet){ | 277 | *gfx->tileset = (Ts_TileSet){ |
243 | .num_tiles = desc->num_tiles, | 278 | .num_tiles = desc->num_tiles, |
244 | }; | 279 | }; |
245 | 280 | ||
246 | iso->iso_space = make_iso_coord_system(iso->map, &iso->screen); | 281 | gfx->iso_space = make_iso_coord_system(gfx->map, &gfx->screen); |
247 | } | 282 | } |
248 | 283 | ||
249 | bool isogfx_load_map(IsoGfx* iso, const char* filepath) { | 284 | bool gfx2d_load_map(Gfx2d* gfx, const char* filepath) { |
250 | assert(iso); | 285 | assert(gfx); |
251 | assert(filepath); | 286 | assert(filepath); |
252 | 287 | ||
253 | bool success = false; | 288 | bool success = false; |
254 | 289 | ||
255 | // Handle recreation by destroying the previous world and sprites. | 290 | // Handle recreation by destroying the previous world and sprites. |
256 | isogfx_clear(iso); | 291 | gfx2d_clear(gfx); |
257 | 292 | ||
258 | // Load the map. | 293 | // Load the map. |
259 | printf("Load tile map: %s\n", filepath); | 294 | printf("Load tile map: %s\n", filepath); |
260 | WITH_FILE(filepath, { | 295 | WITH_FILE(filepath, { |
261 | const size_t map_size = get_file_size_f(file); | 296 | const size_t map_size = get_file_size_f(file); |
262 | iso->map = memstack_alloc_aligned(&iso->stack, map_size, 4); | 297 | gfx->map = memstack_alloc_aligned(&gfx->stack, map_size, 4); |
263 | success = read_file_f(file, iso->map); | 298 | success = read_file_f(file, gfx->map); |
264 | }); | 299 | }); |
265 | if (!success) { | 300 | if (!success) { |
266 | goto cleanup; | 301 | goto cleanup; |
267 | } | 302 | } |
268 | Tm_Map* const map = iso->map; | 303 | Tm_Map* const map = gfx->map; |
269 | 304 | ||
270 | printf("Map orientation: %d\n", ((Tm_Flags*)&map->flags)->orientation); | 305 | printf("Map orientation: %d\n", ((Tm_Flags*)&map->flags)->orientation); |
271 | 306 | ||
@@ -281,39 +316,39 @@ bool isogfx_load_map(IsoGfx* iso, const char* filepath) { | |||
281 | printf("Load tile set: %s\n", ts_path_cwd); | 316 | printf("Load tile set: %s\n", ts_path_cwd); |
282 | WITH_FILE(ts_path_cwd, { | 317 | WITH_FILE(ts_path_cwd, { |
283 | const size_t file_size = get_file_size_f(file); | 318 | const size_t file_size = get_file_size_f(file); |
284 | iso->tileset = memstack_alloc_aligned(&iso->stack, file_size, 4); | 319 | gfx->tileset = memstack_alloc_aligned(&gfx->stack, file_size, 4); |
285 | success = read_file_f(file, iso->tileset); | 320 | success = read_file_f(file, gfx->tileset); |
286 | }); | 321 | }); |
287 | if (!success) { | 322 | if (!success) { |
288 | // TODO: Log errors using the log library. | 323 | // TODO: Log errors using the log library. |
289 | goto cleanup; | 324 | goto cleanup; |
290 | } | 325 | } |
291 | const Ts_TileSet* const tileset = iso->tileset; | 326 | const Ts_TileSet* const tileset = gfx->tileset; |
292 | printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd); | 327 | printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd); |
293 | 328 | ||
294 | // TODO: These assertions on input data should be library runtime errors. | 329 | // TODO: These assertions on input data should be library runtime errors. |
295 | assert(ts_validate_tileset(tileset)); | 330 | assert(ts_validate_tileset(tileset)); |
296 | assert(tm_validate_map(map, tileset)); | 331 | assert(tm_validate_map(map, tileset)); |
297 | 332 | ||
298 | iso->iso_space = make_iso_coord_system(iso->map, &iso->screen); | 333 | gfx->iso_space = make_iso_coord_system(gfx->map, &gfx->screen); |
299 | 334 | ||
300 | success = true; | 335 | success = true; |
301 | 336 | ||
302 | cleanup: | 337 | cleanup: |
303 | if (!success) { | 338 | if (!success) { |
304 | isogfx_clear(iso); | 339 | gfx2d_clear(gfx); |
305 | } | 340 | } |
306 | return success; | 341 | return success; |
307 | } | 342 | } |
308 | 343 | ||
309 | int isogfx_world_width(const IsoGfx* iso) { | 344 | int gfx2d_world_width(const Gfx2d* gfx) { |
310 | assert(iso); | 345 | assert(gfx); |
311 | return iso->map->world_width; | 346 | return gfx->map->world_width; |
312 | } | 347 | } |
313 | 348 | ||
314 | int isogfx_world_height(const IsoGfx* iso) { | 349 | int gfx2d_world_height(const Gfx2d* gfx) { |
315 | assert(iso); | 350 | assert(gfx); |
316 | return iso->map->world_height; | 351 | return gfx->map->world_height; |
317 | } | 352 | } |
318 | 353 | ||
319 | static void make_tile_from_colour( | 354 | static void make_tile_from_colour( |
@@ -325,10 +360,10 @@ static void make_tile_from_colour( | |||
325 | const int height = tile->height; | 360 | const int height = tile->height; |
326 | const int r = width / height; | 361 | const int r = width / height; |
327 | for (int y = 0; y < height / 2; ++y) { | 362 | for (int y = 0; y < height / 2; ++y) { |
328 | const int mask_start = width / 2 - r * y - 1; | 363 | const int mask_start = (width / 2) - (r * y) - 1; |
329 | const int mask_end = width / 2 + r * y + 1; | 364 | const int mask_end = (width / 2) + (r * y) + 1; |
330 | for (int x = 0; x < width; ++x) { | 365 | for (int x = 0; x < width; ++x) { |
331 | const bool mask = (mask_start <= x) && (x <= mask_end); | 366 | const bool mask = ((mask_start <= x) && (x <= mask_end)) != 0; |
332 | const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; | 367 | const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; |
333 | 368 | ||
334 | // Top half. | 369 | // Top half. |
@@ -341,19 +376,19 @@ static void make_tile_from_colour( | |||
341 | } | 376 | } |
342 | } | 377 | } |
343 | 378 | ||
344 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { | 379 | Tile gfx2d_make_tile(Gfx2d* gfx, const TileDesc* desc) { |
345 | assert(iso); | 380 | assert(gfx); |
346 | assert(desc); | 381 | assert(desc); |
347 | // Client must create a world first. | 382 | // Client must create a world first. |
348 | assert(iso->map); | 383 | assert(gfx->map); |
349 | assert(iso->tileset); | 384 | assert(gfx->tileset); |
350 | // Currently, procedural tiles must match the base tile size. | 385 | // Currently, procedural tiles must match the base tile size. |
351 | assert(desc->width == iso->map->base_tile_width); | 386 | assert(desc->width == gfx->map->base_tile_width); |
352 | assert(desc->height == iso->map->base_tile_height); | 387 | assert(desc->height == gfx->map->base_tile_height); |
353 | // Cannot exceed max tiles. | 388 | // Cannot exceed max tiles. |
354 | assert(iso->next_tile < iso->tileset->num_tiles); | 389 | assert(gfx->next_tile < gfx->tileset->num_tiles); |
355 | 390 | ||
356 | const Tile tile = iso->next_tile++; | 391 | const Tile tile = gfx->next_tile++; |
357 | 392 | ||
358 | const size_t tile_size_bytes = desc->width * desc->height * sizeof(Pixel); | 393 | const size_t tile_size_bytes = desc->width * desc->height * sizeof(Pixel); |
359 | 394 | ||
@@ -362,16 +397,16 @@ Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { | |||
362 | assert(desc->width > 0); | 397 | assert(desc->width > 0); |
363 | assert(desc->height > 0); | 398 | assert(desc->height > 0); |
364 | 399 | ||
365 | Ts_Tile* const ts_tile = ts_tileset_get_tile_mut(iso->tileset, tile); | 400 | Ts_Tile* const ts_tile = ts_tileset_get_tile_mut(gfx->tileset, tile); |
366 | 401 | ||
367 | *ts_tile = (Ts_Tile){ | 402 | *ts_tile = (Ts_Tile){ |
368 | .width = iso->map->base_tile_width, | 403 | .width = gfx->map->base_tile_width, |
369 | .height = iso->map->base_tile_height, | 404 | .height = gfx->map->base_tile_height, |
370 | .pixels = tile * tile_size_bytes, | 405 | .pixels = tile * tile_size_bytes, |
371 | }; | 406 | }; |
372 | 407 | ||
373 | Pixel* const tile_pixels = | 408 | Pixel* const tile_pixels = |
374 | ts_tileset_get_tile_pixels_mut(iso->tileset, tile); | 409 | ts_tileset_get_tile_pixels_mut(gfx->tileset, tile); |
375 | make_tile_from_colour(desc->colour, ts_tile, tile_pixels); | 410 | make_tile_from_colour(desc->colour, ts_tile, tile_pixels); |
376 | break; | 411 | break; |
377 | } | 412 | } |
@@ -389,31 +424,31 @@ Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { | |||
389 | return tile; | 424 | return tile; |
390 | } | 425 | } |
391 | 426 | ||
392 | void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { | 427 | void gfx2d_set_tile(Gfx2d* gfx, int x, int y, Tile tile) { |
393 | assert(iso); | 428 | assert(gfx); |
394 | 429 | ||
395 | Tm_Layer* const layer = tm_map_get_layer_mut(iso->map, 0); | 430 | Tm_Layer* const layer = tm_map_get_layer_mut(gfx->map, 0); |
396 | Tile* map_tile = tm_layer_get_tile_mut(iso->map, layer, x, y); | 431 | Tile* map_tile = tm_layer_get_tile_mut(gfx->map, layer, x, y); |
397 | 432 | ||
398 | *map_tile = tile; | 433 | *map_tile = tile; |
399 | } | 434 | } |
400 | 435 | ||
401 | void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { | 436 | void gfx2d_set_tiles(Gfx2d* gfx, int x0, int y0, int x1, int y1, Tile tile) { |
402 | assert(iso); | 437 | assert(gfx); |
403 | for (int y = y0; y < y1; ++y) { | 438 | for (int y = y0; y < y1; ++y) { |
404 | for (int x = x0; x < x1; ++x) { | 439 | for (int x = x0; x < x1; ++x) { |
405 | isogfx_set_tile(iso, x, y, tile); | 440 | gfx2d_set_tile(gfx, x, y, tile); |
406 | } | 441 | } |
407 | } | 442 | } |
408 | } | 443 | } |
409 | 444 | ||
410 | SpriteSheet isogfx_load_sprite_sheet(IsoGfx* iso, const char* filepath) { | 445 | SpriteSheet gfx2d_load_sprite_sheet(Gfx2d* gfx, const char* filepath) { |
411 | assert(iso); | 446 | assert(gfx); |
412 | assert(filepath); | 447 | assert(filepath); |
413 | 448 | ||
414 | bool success = false; | 449 | bool success = false; |
415 | SpriteSheet spriteSheet = 0; | 450 | SpriteSheet spriteSheet = 0; |
416 | const size_t watermark = memstack_watermark(&iso->stack); | 451 | const size_t watermark = memstack_watermark(&gfx->stack); |
417 | 452 | ||
418 | // Load sprite sheet file. | 453 | // Load sprite sheet file. |
419 | printf("Load sprite sheet: %s\n", filepath); | 454 | printf("Load sprite sheet: %s\n", filepath); |
@@ -421,7 +456,7 @@ SpriteSheet isogfx_load_sprite_sheet(IsoGfx* iso, const char* filepath) { | |||
421 | WITH_FILE(filepath, { | 456 | WITH_FILE(filepath, { |
422 | const size_t file_size = get_file_size_f(file); | 457 | const size_t file_size = get_file_size_f(file); |
423 | ss_sheet = | 458 | ss_sheet = |
424 | memstack_alloc_aligned(&iso->stack, file_size, alignof(Ss_SpriteSheet)); | 459 | memstack_alloc_aligned(&gfx->stack, file_size, alignof(Ss_SpriteSheet)); |
425 | success = read_file_f(file, ss_sheet); | 460 | success = read_file_f(file, ss_sheet); |
426 | }); | 461 | }); |
427 | if (!success) { | 462 | if (!success) { |
@@ -434,63 +469,63 @@ SpriteSheet isogfx_load_sprite_sheet(IsoGfx* iso, const char* filepath) { | |||
434 | cleanup: | 469 | cleanup: |
435 | if (!success) { | 470 | if (!success) { |
436 | if (ss_sheet) { | 471 | if (ss_sheet) { |
437 | memstack_set_watermark(&iso->stack, watermark); | 472 | memstack_set_watermark(&gfx->stack, watermark); |
438 | } | 473 | } |
439 | } | 474 | } |
440 | return spriteSheet; | 475 | return spriteSheet; |
441 | } | 476 | } |
442 | 477 | ||
443 | Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) { | 478 | Sprite gfx2d_make_sprite(Gfx2d* gfx, SpriteSheet sheet) { |
444 | assert(iso); | 479 | assert(gfx); |
445 | assert(sheet); | 480 | assert(sheet); |
446 | 481 | ||
447 | // TODO: Remove memstack_alloc() and replace it with a same-name macro that | 482 | // TODO: Remove memstack_alloc() and replace it with a same-name macro that |
448 | // calls memstack_alloc_aligned() with sizeof/alignof. No real point in | 483 | // calls memstack_alloc_aligned() with sizeof/alignof. No real point in |
449 | // having unaligned allocations. | 484 | // having unaligned allocations. |
450 | SpriteInstance* sprite = memstack_alloc_aligned( | 485 | SpriteInstance* sprite = memstack_alloc_aligned( |
451 | &iso->stack, sizeof(SpriteInstance), alignof(SpriteInstance)); | 486 | &gfx->stack, sizeof(SpriteInstance), alignof(SpriteInstance)); |
452 | 487 | ||
453 | sprite->sheet = (const Ss_SpriteSheet*)sheet; | 488 | sprite->sheet = (const Ss_SpriteSheet*)sheet; |
454 | sprite->next = iso->head_sprite; | 489 | sprite->next = gfx->head_sprite; |
455 | iso->head_sprite = sprite; | 490 | gfx->head_sprite = sprite; |
456 | 491 | ||
457 | return (Sprite)sprite; | 492 | return (Sprite)sprite; |
458 | } | 493 | } |
459 | 494 | ||
460 | void isogfx_set_sprite_position(IsoGfx* iso, Sprite hSprite, int x, int y) { | 495 | void gfx2d_set_sprite_position(Gfx2d* gfx, Sprite hSprite, int x, int y) { |
461 | assert(iso); | 496 | assert(gfx); |
462 | SpriteInstance* sprite = (SpriteInstance*)hSprite; | 497 | SpriteInstance* sprite = (SpriteInstance*)hSprite; |
463 | sprite->position.x = x; | 498 | sprite->position.x = x; |
464 | sprite->position.y = y; | 499 | sprite->position.y = y; |
465 | } | 500 | } |
466 | 501 | ||
467 | void isogfx_set_sprite_animation(IsoGfx* iso, Sprite hSprite, int animation) { | 502 | void gfx2d_set_sprite_animation(Gfx2d* gfx, Sprite hSprite, int animation) { |
468 | assert(iso); | 503 | assert(gfx); |
469 | SpriteInstance* sprite = (SpriteInstance*)hSprite; | 504 | SpriteInstance* sprite = (SpriteInstance*)hSprite; |
470 | sprite->animation = animation; | 505 | sprite->animation = animation; |
471 | } | 506 | } |
472 | 507 | ||
473 | void isogfx_update(IsoGfx* iso, double t) { | 508 | void gfx2d_update(Gfx2d* gfx, double t) { |
474 | assert(iso); | 509 | assert(gfx); |
475 | 510 | ||
476 | // If this is the first time update() is called after initialization, just | 511 | // If this is the first time update() is called after initialization, just |
477 | // record the starting animation time. | 512 | // record the starting animation time. |
478 | if (iso->last_animation_time == 0.0) { | 513 | if (gfx->last_animation_time == 0.0) { |
479 | iso->last_animation_time = t; | 514 | gfx->last_animation_time = t; |
480 | return; | 515 | return; |
481 | } | 516 | } |
482 | 517 | ||
483 | if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) { | 518 | if ((t - gfx->last_animation_time) >= ANIMATION_UPDATE_DELTA) { |
484 | // TODO: Consider linking animated sprites in a separate list so that we | 519 | // TODO: Consider linking animated sprites in a separate list so that we |
485 | // only walk over those here and not also the static sprites. | 520 | // only walk over those here and not also the static sprites. |
486 | for (SpriteInstance* sprite = iso->head_sprite; sprite; | 521 | for (SpriteInstance* sprite = gfx->head_sprite; sprite; |
487 | sprite = sprite->next) { | 522 | sprite = sprite->next) { |
488 | const Ss_SpriteSheet* sheet = sprite->sheet; | 523 | const Ss_SpriteSheet* sheet = sprite->sheet; |
489 | const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); | 524 | const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); |
490 | sprite->frame = (sprite->frame + 1) % row->num_cols; | 525 | sprite->frame = (sprite->frame + 1) % row->num_cols; |
491 | } | 526 | } |
492 | 527 | ||
493 | iso->last_animation_time = t; | 528 | gfx->last_animation_time = t; |
494 | } | 529 | } |
495 | } | 530 | } |
496 | 531 | ||
@@ -498,18 +533,6 @@ void isogfx_update(IsoGfx* iso, double t) { | |||
498 | // Rendering and picking. | 533 | // Rendering and picking. |
499 | // ----------------------------------------------------------------------------- | 534 | // ----------------------------------------------------------------------------- |
500 | 535 | ||
501 | /// Get the screen position of the top diamond-corner of the tile at world | ||
502 | /// (x,y). | ||
503 | static ivec2 GetTileScreenOrigin( | ||
504 | const CoordSystem iso_space, ivec2 camera, int world_x, int world_y) { | ||
505 | const ivec2 vx_offset = ivec2_scale(iso_space.x, world_x); | ||
506 | const ivec2 vy_offset = ivec2_scale(iso_space.y, world_y); | ||
507 | const ivec2 screen_origin = | ||
508 | ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset)); | ||
509 | const ivec2 origin_view_space = ivec2_add(screen_origin, ivec2_neg(camera)); | ||
510 | return origin_view_space; | ||
511 | } | ||
512 | |||
513 | static Pixel alpha_blend(Pixel src, Pixel dst) { | 536 | static Pixel alpha_blend(Pixel src, Pixel dst) { |
514 | if ((src.a == 255) || (dst.a == 0)) { | 537 | if ((src.a == 255) || (dst.a == 0)) { |
515 | return src; | 538 | return src; |
@@ -541,13 +564,13 @@ static void draw_rect( | |||
541 | Screen* screen, ivec2 top_left, int rect_width, int rect_height, | 564 | Screen* screen, ivec2 top_left, int rect_width, int rect_height, |
542 | const Pixel* pixels, const uint8_t* indices) { | 565 | const Pixel* pixels, const uint8_t* indices) { |
543 | assert(screen); | 566 | assert(screen); |
567 | assert(pixels); | ||
544 | 568 | ||
545 | #define rect_pixel(X, Y) \ | 569 | #define rect_pixel(X, Y) \ |
546 | (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X]) | 570 | (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X]) |
547 | 571 | ||
548 | // Rect origin can be outside screen bounds, so we must offset accordingly to | 572 | // Rect origin can be outside screen bounds, so we must offset accordingly to |
549 | // draw only the visible portion. | 573 | // draw only the visible portion. |
550 | #define max(a, b) (a > b ? a : b) | ||
551 | const int px_offset = max(0, -top_left.x); | 574 | const int px_offset = max(0, -top_left.x); |
552 | const int py_offset = max(0, -top_left.y); | 575 | const int py_offset = max(0, -top_left.y); |
553 | 576 | ||
@@ -568,137 +591,269 @@ static void draw_rect( | |||
568 | } | 591 | } |
569 | } | 592 | } |
570 | 593 | ||
571 | /// Draw a tile. | 594 | /// Draw a tile in an orthogonal map. |
572 | /// | 595 | static void draw_tile_ortho(Gfx2d* gfx, Tile tile, int x, int y) { |
573 | /// 'screen_origin' is the screen coordinates of the top diamond-corner of the | 596 | assert(gfx); |
574 | /// tile (the base tile for super tiles). | 597 | assert(gfx->tileset); |
575 | /// World (0, 0) -> (screen_width / 2, 0). | 598 | assert(x >= 0); |
576 | static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) { | 599 | assert(y >= 0); |
577 | assert(iso); | 600 | assert(x < gfx->map->world_width); |
578 | assert(iso->tileset); | 601 | assert(y < gfx->map->world_height); |
602 | |||
603 | const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile); | ||
604 | const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile); | ||
605 | |||
606 | const ivec2 screen_origin = map2screen( | ||
607 | gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, x, y); | ||
608 | |||
609 | draw_rect( | ||
610 | &gfx->screen, screen_origin, pTile->width, pTile->height, pixels, | ||
611 | nullptr); | ||
612 | } | ||
579 | 613 | ||
580 | const Ts_Tile* pTile = ts_tileset_get_tile(iso->tileset, tile); | 614 | /// Draw a tile in an isometric map. |
581 | const Pixel* pixels = ts_tileset_get_tile_pixels(iso->tileset, tile); | 615 | static void draw_tile_iso(Gfx2d* gfx, Tile tile, int iso_x, int iso_y) { |
616 | assert(gfx); | ||
617 | assert(gfx->tileset); | ||
618 | assert(iso_x >= 0); | ||
619 | assert(iso_y >= 0); | ||
620 | assert(iso_x < gfx->map->world_width); | ||
621 | assert(iso_y < gfx->map->world_height); | ||
622 | |||
623 | const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile); | ||
624 | const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile); | ||
625 | |||
626 | // Compute the screen coordinates of the top diamond-corner of the tile (the | ||
627 | // base tile for super tiles). | ||
628 | // World (0, 0) -> (screen_width / 2, 0). | ||
629 | const ivec2 screen_origin = | ||
630 | iso2cart(gfx->iso_space, gfx->camera, iso_x, iso_y); | ||
582 | 631 | ||
583 | // Move from the top diamond-corner to the top-left corner of the tile image. | 632 | // Move from the top diamond-corner to the top-left corner of the tile image. |
584 | // For regular tiles, tile height == base tile height, so the y offset is 0. | 633 | // For regular tiles, tile height == base tile height, so the y offset is 0. |
585 | // For super tiles, move as high up as the height of the tile. | 634 | // For super tiles, move as high up as the height of the tile. |
586 | const ivec2 offset = { | 635 | const ivec2 offset = { |
587 | -(iso->map->base_tile_width / 2), | 636 | -(gfx->map->base_tile_width / 2), |
588 | pTile->height - iso->map->base_tile_height}; | 637 | pTile->height - gfx->map->base_tile_height}; |
589 | const ivec2 top_left = ivec2_add(screen_origin, offset); | 638 | const ivec2 top_left = ivec2_add(screen_origin, offset); |
590 | 639 | ||
591 | draw_rect( | 640 | draw_rect( |
592 | &iso->screen, top_left, pTile->width, pTile->height, pixels, nullptr); | 641 | &gfx->screen, top_left, pTile->width, pTile->height, pixels, nullptr); |
593 | } | 642 | } |
594 | 643 | ||
595 | static void draw_map(IsoGfx* iso) { | 644 | static void draw_map_ortho(Gfx2d* gfx) { |
596 | assert(iso); | 645 | assert(gfx); |
646 | assert(gfx->map); | ||
647 | |||
648 | // TODO: Same TODOs as in draw_map_iso(). | ||
597 | 649 | ||
598 | const int W = iso->screen.width; | 650 | const Tm_Layer* layer = tm_map_get_layer(gfx->map, 0); |
599 | const int H = iso->screen.height; | ||
600 | 651 | ||
601 | memset(iso->screen.pixels, 0, W * H * sizeof(Pixel)); | 652 | for (int wy = 0; wy < gfx->map->world_height; ++wy) { |
653 | for (int wx = 0; wx < gfx->map->world_width; ++wx) { | ||
654 | const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy); | ||
655 | draw_tile_ortho(gfx, tile, wx, wy); | ||
656 | } | ||
657 | } | ||
658 | } | ||
602 | 659 | ||
603 | const Tm_Layer* layer = tm_map_get_layer(iso->map, 0); | 660 | static void draw_map_iso(Gfx2d* gfx) { |
661 | assert(gfx); | ||
662 | assert(gfx->map); | ||
663 | |||
664 | // TODO: Support for multiple layers. | ||
665 | const Tm_Layer* layer = tm_map_get_layer(gfx->map, 0); | ||
604 | 666 | ||
605 | // TODO: Culling. | 667 | // TODO: Culling. |
606 | // Ex: map the screen corners to tile space to cull. | 668 | // Ex: map the screen corners to tile space to cull. |
607 | // Ex: walk in screen space and fetch the tile. | 669 | // Ex: walk in screen space and fetch the tile. |
608 | // The tile-centric approach might be more cache-friendly since the | 670 | // The tile-centric approach might be more cache-friendly since the |
609 | // screen-centric approach would juggle multiple tiles throughout the scan. | 671 | // screen-centric approach would juggle multiple tiles throughout the scan. |
610 | for (int wy = 0; wy < iso->map->world_height; ++wy) { | 672 | for (int wy = 0; wy < gfx->map->world_height; ++wy) { |
611 | for (int wx = 0; wx < iso->map->world_width; ++wx) { | 673 | for (int wx = 0; wx < gfx->map->world_width; ++wx) { |
612 | const Tile tile = tm_layer_get_tile(iso->map, layer, wx, wy); | 674 | const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy); |
613 | const ivec2 screen_origin = | 675 | draw_tile_iso(gfx, tile, wx, wy); |
614 | GetTileScreenOrigin(iso->iso_space, iso->camera, wx, wy); | ||
615 | draw_tile(iso, screen_origin, tile); | ||
616 | } | 676 | } |
617 | } | 677 | } |
618 | } | 678 | } |
619 | 679 | ||
620 | static void draw_sprite( | 680 | static void draw_map(Gfx2d* gfx) { |
621 | IsoGfx* iso, ivec2 origin, const SpriteInstance* sprite, | 681 | assert(gfx); |
622 | const Ss_SpriteSheet* sheet) { | 682 | assert(gfx->map); |
623 | assert(iso); | 683 | assert(gfx->screen.pixels); |
684 | |||
685 | const int W = gfx->screen.width; | ||
686 | const int H = gfx->screen.height; | ||
687 | |||
688 | memset(gfx->screen.pixels, 0, W * H * sizeof(Pixel)); | ||
689 | |||
690 | const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags; | ||
691 | switch (flags->orientation) { | ||
692 | case Tm_Orthogonal: | ||
693 | draw_map_ortho(gfx); | ||
694 | break; | ||
695 | case Tm_Isometric: | ||
696 | draw_map_iso(gfx); | ||
697 | break; | ||
698 | } | ||
699 | } | ||
700 | |||
701 | /// Draw a sprite in an orthogonal/Cartesian coordinate system. | ||
702 | static void draw_sprite_ortho( | ||
703 | Gfx2d* gfx, const SpriteInstance* sprite, const Ss_SpriteSheet* sheet) { | ||
704 | assert(gfx); | ||
624 | assert(sprite); | 705 | assert(sprite); |
625 | assert(sheet); | 706 | assert(sheet); |
626 | assert(sprite->animation >= 0); | 707 | assert(sprite->animation >= 0); |
627 | assert(sprite->animation < sheet->num_rows); | 708 | assert(sprite->animation < sheet->num_rows); |
628 | assert(sprite->frame >= 0); | 709 | assert(sprite->frame >= 0); |
629 | 710 | ||
711 | // Apply an offset similarly to how we offset tiles. The sprite is offset by | ||
712 | // -base_tile_width/2 along the x-axis to align the sprite with the leftmost | ||
713 | // edge of the tile it is on. | ||
714 | const ivec2 screen_origin = map2screen( | ||
715 | gfx->camera, gfx->map->base_tile_width, gfx->map->base_tile_height, | ||
716 | sprite->position.x, sprite->position.y); | ||
717 | |||
630 | const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); | 718 | const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); |
631 | const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame); | 719 | const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame); |
632 | draw_rect( | 720 | draw_rect( |
633 | &iso->screen, origin, sheet->sprite_width, sheet->sprite_height, | 721 | &gfx->screen, screen_origin, sheet->sprite_width, sheet->sprite_height, |
634 | sheet->palette.colours, frame); | 722 | sheet->palette.colours, frame); |
635 | } | 723 | } |
636 | 724 | ||
637 | static void draw_sprites(IsoGfx* iso) { | 725 | /// Draw a sprite in an isometric coordinate system. |
638 | assert(iso); | 726 | static void draw_sprite_iso( |
727 | Gfx2d* gfx, const SpriteInstance* sprite, const Ss_SpriteSheet* sheet) { | ||
728 | assert(gfx); | ||
729 | assert(sprite); | ||
730 | assert(sheet); | ||
731 | assert(sprite->animation >= 0); | ||
732 | assert(sprite->animation < sheet->num_rows); | ||
733 | assert(sprite->frame >= 0); | ||
639 | 734 | ||
640 | for (const SpriteInstance* sprite = iso->head_sprite; sprite; | 735 | // Apply an offset similarly to how we offset tiles. The sprite is offset by |
641 | sprite = sprite->next) { | 736 | // -base_tile_width/2 along the x-axis to align the sprite with the leftmost |
642 | const Ss_SpriteSheet* sheet = sprite->sheet; | 737 | // edge of the tile it is on. |
643 | assert(sheet); | 738 | const ivec2 screen_origin = iso2cart( |
739 | gfx->iso_space, gfx->camera, sprite->position.x, sprite->position.y); | ||
740 | const ivec2 offset = {-(gfx->map->base_tile_width / 2), 0}; | ||
741 | const ivec2 top_left = ivec2_add(screen_origin, offset); | ||
644 | 742 | ||
645 | const ivec2 screen_origin = GetTileScreenOrigin( | 743 | const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); |
646 | iso->iso_space, iso->camera, sprite->position.x, sprite->position.y); | 744 | const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame); |
647 | draw_sprite(iso, screen_origin, sprite, sheet); | 745 | draw_rect( |
746 | &gfx->screen, top_left, sheet->sprite_width, sheet->sprite_height, | ||
747 | sheet->palette.colours, frame); | ||
748 | } | ||
749 | |||
750 | static void draw_sprites(Gfx2d* gfx) { | ||
751 | assert(gfx); | ||
752 | assert(gfx->map); | ||
753 | |||
754 | const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags; | ||
755 | switch (flags->orientation) { | ||
756 | case Tm_Orthogonal: | ||
757 | for (const SpriteInstance* sprite = gfx->head_sprite; sprite; | ||
758 | sprite = sprite->next) { | ||
759 | draw_sprite_ortho(gfx, sprite, sprite->sheet); | ||
760 | } | ||
761 | break; | ||
762 | case Tm_Isometric: | ||
763 | for (const SpriteInstance* sprite = gfx->head_sprite; sprite; | ||
764 | sprite = sprite->next) { | ||
765 | draw_sprite_iso(gfx, sprite, sprite->sheet); | ||
766 | } | ||
767 | break; | ||
648 | } | 768 | } |
649 | } | 769 | } |
650 | 770 | ||
651 | void isogfx_set_camera(IsoGfx* iso, int x, int y) { | 771 | void gfx2d_render(Gfx2d* gfx, int camera_x, int camera_y) { |
652 | assert(iso); | 772 | assert(gfx); |
653 | iso->camera = (ivec2){x, y}; | 773 | // Storing the camera mostly for debugging convenience. It could otherwise be |
774 | // passed around. | ||
775 | gfx->camera = (ivec2){camera_x, camera_y}; | ||
776 | draw_map(gfx); | ||
777 | draw_sprites(gfx); | ||
778 | } | ||
779 | |||
780 | void gfx2d_draw_tile(Gfx2d* gfx, int x, int y, Tile tile) { | ||
781 | assert(gfx); | ||
782 | assert(gfx->map); | ||
783 | |||
784 | const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags; | ||
785 | switch (flags->orientation) { | ||
786 | case Tm_Orthogonal: | ||
787 | draw_tile_ortho(gfx, tile, x, y); | ||
788 | break; | ||
789 | case Tm_Isometric: | ||
790 | draw_tile_iso(gfx, tile, x, y); | ||
791 | break; | ||
792 | } | ||
654 | } | 793 | } |
655 | 794 | ||
656 | void isogfx_render(IsoGfx* iso) { | 795 | static void clip_camera_ortho(const Gfx2d* gfx, float* x, float* y) { |
657 | assert(iso); | 796 | assert(gfx); |
658 | draw_map(iso); | 797 | assert(gfx->map); |
659 | draw_sprites(iso); | 798 | |
799 | if (x != nullptr) { | ||
800 | const int width_pixels = gfx->map->world_width * gfx->map->base_tile_width; | ||
801 | const int x_max = width_pixels - gfx->screen.width; | ||
802 | *x = min((float)x_max, max(0.F, *x)); | ||
803 | } | ||
804 | if (y != nullptr) { | ||
805 | const int height_pixels = | ||
806 | gfx->map->world_height * gfx->map->base_tile_height; | ||
807 | const int y_max = height_pixels - gfx->screen.height; | ||
808 | *y = min((float)y_max, max(0.F, *y)); | ||
809 | } | ||
660 | } | 810 | } |
661 | 811 | ||
662 | void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) { | 812 | void gfx2d_clip_camera(const Gfx2d* gfx, float* x, float* y) { |
663 | assert(iso); | 813 | assert(gfx); |
664 | assert(x >= 0); | 814 | assert(gfx->map); |
665 | assert(y >= 0); | 815 | assert(x); |
666 | assert(x < iso->map->world_width); | 816 | assert(y); |
667 | assert(y < iso->map->world_height); | ||
668 | 817 | ||
669 | const ivec2 screen_origin = | 818 | const Tm_Flags* flags = (const Tm_Flags*)&gfx->map->flags; |
670 | GetTileScreenOrigin(iso->iso_space, iso->camera, x, y); | 819 | switch (flags->orientation) { |
671 | draw_tile(iso, screen_origin, tile); | 820 | case Tm_Orthogonal: |
821 | clip_camera_ortho(gfx, x, y); | ||
822 | break; | ||
823 | case Tm_Isometric: | ||
824 | // TODO: Clip camera in isometric maps. | ||
825 | break; | ||
826 | } | ||
672 | } | 827 | } |
673 | 828 | ||
674 | void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) { | 829 | void gfx2d_get_screen_size(const Gfx2d* gfx, int* width, int* height) { |
675 | assert(iso); | 830 | assert(gfx); |
676 | assert(width); | 831 | assert(width); |
677 | assert(height); | 832 | assert(height); |
678 | *width = iso->screen.width; | 833 | *width = gfx->screen.width; |
679 | *height = iso->screen.height; | 834 | *height = gfx->screen.height; |
680 | } | 835 | } |
681 | 836 | ||
682 | const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) { | 837 | const Pixel* gfx2d_get_screen_buffer(const Gfx2d* gfx) { |
683 | assert(iso); | 838 | assert(gfx); |
684 | return iso->screen.pixels; | 839 | return gfx->screen.pixels; |
685 | } | 840 | } |
686 | 841 | ||
687 | void isogfx_pick_tile( | 842 | void gfx2d_pick_tile( |
688 | const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { | 843 | const Gfx2d* gfx, double xcart, double ycart, int* xiso, int* yiso) { |
689 | assert(iso); | 844 | assert(gfx); |
690 | assert(xiso); | 845 | assert(xiso); |
691 | assert(yiso); | 846 | assert(yiso); |
692 | 847 | ||
693 | const vec2 camera = ivec2_to_vec2(iso->camera); | 848 | const vec2 camera = ivec2_to_vec2(gfx->camera); |
694 | const vec2 xy_cart = vec2_add(camera, (vec2){xcart, ycart}); | 849 | const vec2 xy_cart = vec2_add(camera, (vec2){xcart, ycart}); |
695 | 850 | ||
696 | const vec2 xy_iso = cart2iso( | 851 | const vec2 xy_iso = cart2iso( |
697 | xy_cart, iso->map->base_tile_width, iso->map->base_tile_height, | 852 | xy_cart, gfx->map->base_tile_width, gfx->map->base_tile_height, |
698 | iso->screen.width); | 853 | gfx->screen.width); |
699 | 854 | ||
700 | if ((0 <= xy_iso.x) && (xy_iso.x < iso->map->world_width) && | 855 | if ((0 <= xy_iso.x) && (xy_iso.x < gfx->map->world_width) && |
701 | (0 <= xy_iso.y) && (xy_iso.y < iso->map->world_height)) { | 856 | (0 <= xy_iso.y) && (xy_iso.y < gfx->map->world_height)) { |
702 | *xiso = (int)xy_iso.x; | 857 | *xiso = (int)xy_iso.x; |
703 | *yiso = (int)xy_iso.y; | 858 | *yiso = (int)xy_iso.y; |
704 | } else { | 859 | } else { |