summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt4
-rw-r--r--demos/checkerboard/CMakeLists.txt2
-rw-r--r--demos/checkerboard/checkerboard.c32
-rw-r--r--demos/isomap/CMakeLists.txt2
-rw-r--r--demos/isomap/isomap.c41
-rw-r--r--include/isogfx/asset.h30
-rw-r--r--include/isogfx/gfx2d.h (renamed from include/isogfx/isogfx.h)24
-rw-r--r--src/backend.c2
-rw-r--r--src/gfx2d.c (renamed from src/isogfx.c)106
-rw-r--r--tools/mkasset.py215
10 files changed, 290 insertions, 168 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 72429c9..78aefa3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required(VERSION 3.0) 1cmake_minimum_required(VERSION 3.5)
2 2
3project(isogfx) 3project(isogfx)
4 4
@@ -10,7 +10,7 @@ set(CMAKE_C_EXTENSIONS Off)
10 10
11add_library(isogfx 11add_library(isogfx
12 src/asset.c 12 src/asset.c
13 src/isogfx.c) 13 src/gfx2d.c)
14 14
15target_include_directories(isogfx PUBLIC 15target_include_directories(isogfx PUBLIC
16 include) 16 include)
diff --git a/demos/checkerboard/CMakeLists.txt b/demos/checkerboard/CMakeLists.txt
index d1691c6..b4e3a2a 100644
--- a/demos/checkerboard/CMakeLists.txt
+++ b/demos/checkerboard/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required(VERSION 3.0) 1cmake_minimum_required(VERSION 3.5)
2 2
3project(checkerboard) 3project(checkerboard)
4 4
diff --git a/demos/checkerboard/checkerboard.c b/demos/checkerboard/checkerboard.c
index b408fc2..4f43526 100644
--- a/demos/checkerboard/checkerboard.c
+++ b/demos/checkerboard/checkerboard.c
@@ -1,5 +1,5 @@
1#include <isogfx/backend.h> 1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h> 2#include <isogfx/gfx2d.h>
3 3
4#include <gfx/app.h> 4#include <gfx/app.h>
5 5
@@ -66,9 +66,9 @@ static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) {
66 } 66 }
67} 67}
68 68
69static bool init(GfxAppState* state, int argc, const char** argv) { 69static bool init(GfxApp* app, GfxAppState* state, int argc, const char** argv) {
70 assert(app);
70 assert(state); 71 assert(state);
71
72 (void)argc; 72 (void)argc;
73 (void)argv; 73 (void)argv;
74 74
@@ -81,12 +81,12 @@ static bool init(GfxAppState* state, int argc, const char** argv) {
81 } 81 }
82 IsoGfx* iso = state->iso; 82 IsoGfx* iso = state->iso;
83 83
84 isogfx_make_world( 84 isogfx_make_map(
85 iso, &(WorldDesc){.tile_width = TILE_WIDTH, 85 iso, &(MapDesc){.tile_width = TILE_WIDTH,
86 .tile_height = TILE_HEIGHT, 86 .tile_height = TILE_HEIGHT,
87 .world_width = WORLD_WIDTH, 87 .world_width = WORLD_WIDTH,
88 .world_height = WORLD_HEIGHT, 88 .world_height = WORLD_HEIGHT,
89 .num_tiles = NUM_TILES}); 89 .num_tiles = NUM_TILES});
90 90
91 const Tile black = isogfx_make_tile(iso, &tile_set[Black]); 91 const Tile black = isogfx_make_tile(iso, &tile_set[Black]);
92 const Tile white = isogfx_make_tile(iso, &tile_set[White]); 92 const Tile white = isogfx_make_tile(iso, &tile_set[White]);
@@ -100,14 +100,16 @@ static bool init(GfxAppState* state, int argc, const char** argv) {
100 return true; 100 return true;
101} 101}
102 102
103static void shutdown(GfxAppState* state) { 103static void shutdown(GfxApp* app, GfxAppState* state) {
104 assert(app);
104 assert(state); 105 assert(state);
105 106
106 iso_backend_shutdown(&state->backend); 107 iso_backend_shutdown(&state->backend);
107 isogfx_del(&state->iso); 108 isogfx_del(&state->iso);
108} 109}
109 110
110static void update(GfxAppState* state, double t, double dt) { 111static void update(GfxApp* app, GfxAppState* state, double t, double dt) {
112 assert(app);
111 assert(state); 113 assert(state);
112 (void)dt; 114 (void)dt;
113 115
@@ -117,7 +119,7 @@ static void update(GfxAppState* state, double t, double dt) {
117 119
118 // Get mouse position in window coordinates. 120 // Get mouse position in window coordinates.
119 double mouse_x, mouse_y; 121 double mouse_x, mouse_y;
120 gfx_app_get_mouse_position(&mouse_x, &mouse_y); 122 gfx_app_get_mouse_position(app, &mouse_x, &mouse_y);
121 123
122 // Map from window coordinates to virtual screen coordinates. 124 // Map from window coordinates to virtual screen coordinates.
123 iso_backend_get_mouse_position( 125 iso_backend_get_mouse_position(
@@ -128,7 +130,8 @@ static void update(GfxAppState* state, double t, double dt) {
128 printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); 130 printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick);
129} 131}
130 132
131static void render(GfxAppState* state) { 133static void render(const GfxApp* app, GfxAppState* state) {
134 assert(app);
132 assert(state); 135 assert(state);
133 136
134 IsoGfx* iso = state->iso; 137 IsoGfx* iso = state->iso;
@@ -142,7 +145,8 @@ static void render(GfxAppState* state) {
142 iso_backend_render(state->backend, iso); 145 iso_backend_render(state->backend, iso);
143} 146}
144 147
145static void resize(GfxAppState* state, int width, int height) { 148static void resize(GfxApp* app, GfxAppState* state, int width, int height) {
149 assert(app);
146 assert(state); 150 assert(state);
147 151
148 iso_backend_resize_window(state->backend, state->iso, width, height); 152 iso_backend_resize_window(state->backend, state->iso, width, height);
diff --git a/demos/isomap/CMakeLists.txt b/demos/isomap/CMakeLists.txt
index 2dbfd32..9467625 100644
--- a/demos/isomap/CMakeLists.txt
+++ b/demos/isomap/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required(VERSION 3.0) 1cmake_minimum_required(VERSION 3.5)
2 2
3project(isomap) 3project(isomap)
4 4
diff --git a/demos/isomap/isomap.c b/demos/isomap/isomap.c
index a940535..bca27f6 100644
--- a/demos/isomap/isomap.c
+++ b/demos/isomap/isomap.c
@@ -1,5 +1,5 @@
1#include <isogfx/backend.h> 1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h> 2#include <isogfx/gfx2d.h>
3 3
4#include <gfx/app.h> 4#include <gfx/app.h>
5#include <math/vec2.h> 5#include <math/vec2.h>
@@ -16,9 +16,9 @@ static const int MAX_FPS = 60;
16static const int SCREEN_WIDTH = 704; 16static const int SCREEN_WIDTH = 704;
17static const int SCREEN_HEIGHT = 480; 17static const int SCREEN_HEIGHT = 480;
18 18
19static const R CAMERA_SPEED = 400; 19static const R CAMERA_SPEED = 800;
20 20
21#define MEMORY_SIZE (2 * 1024 * 1024) 21#define MEMORY_SIZE (8 * 1024 * 1024)
22uint8_t MEMORY[MEMORY_SIZE]; 22uint8_t MEMORY[MEMORY_SIZE];
23 23
24typedef struct GfxAppState { 24typedef struct GfxAppState {
@@ -31,7 +31,8 @@ typedef struct GfxAppState {
31 Sprite stag; 31 Sprite stag;
32} GfxAppState; 32} GfxAppState;
33 33
34static bool init(GfxAppState* state, int argc, const char** argv) { 34static bool init(GfxApp* app, GfxAppState* state, int argc, const char** argv) {
35 assert(app);
35 assert(state); 36 assert(state);
36 (void)argc; 37 (void)argc;
37 (void)argv; 38 (void)argv;
@@ -45,7 +46,8 @@ static bool init(GfxAppState* state, int argc, const char** argv) {
45 } 46 }
46 IsoGfx* iso = state->iso; 47 IsoGfx* iso = state->iso;
47 48
48 if (!isogfx_load_world(iso, "/home/jeanne/Nextcloud/assets/maps/demo-1.tm")) { 49 if (!isogfx_load_map(
50 iso, "/home/jeanne/Nextcloud/assets/tilemaps/scrabling1.tm")) {
49 return false; 51 return false;
50 } 52 }
51 53
@@ -57,7 +59,7 @@ static bool init(GfxAppState* state, int argc, const char** argv) {
57 } 59 }
58 60
59 state->stag = isogfx_make_sprite(iso, state->stag_sheet); 61 state->stag = isogfx_make_sprite(iso, state->stag_sheet);
60 isogfx_set_sprite_position(iso, state->stag, 5, 4); 62 isogfx_set_sprite_position(iso, state->stag, 0, 0);
61 63
62 if (!((state->backend = iso_backend_init(iso)))) { 64 if (!((state->backend = iso_backend_init(iso)))) {
63 return false; 65 return false;
@@ -66,23 +68,25 @@ static bool init(GfxAppState* state, int argc, const char** argv) {
66 return true; 68 return true;
67} 69}
68 70
69static void shutdown(GfxAppState* state) { 71static void shutdown(GfxApp* app, GfxAppState* state) {
72 assert(app);
70 assert(state); 73 assert(state);
71 //
72} 74}
73 75
74static vec2 get_camera_movement(R dt) { 76static vec2 get_camera_movement(GfxApp* app, R dt) {
77 assert(app);
78
75 vec2 offset = {0}; 79 vec2 offset = {0};
76 if (gfx_app_is_key_pressed(KeyA)) { 80 if (gfx_app_is_key_pressed(app, KeyA)) {
77 offset.x -= 1; 81 offset.x -= 1;
78 } 82 }
79 if (gfx_app_is_key_pressed(KeyD)) { 83 if (gfx_app_is_key_pressed(app, KeyD)) {
80 offset.x += 1; 84 offset.x += 1;
81 } 85 }
82 if (gfx_app_is_key_pressed(KeyW)) { 86 if (gfx_app_is_key_pressed(app, KeyW)) {
83 offset.y -= 1; 87 offset.y -= 1;
84 } 88 }
85 if (gfx_app_is_key_pressed(KeyS)) { 89 if (gfx_app_is_key_pressed(app, KeyS)) {
86 offset.y += 1; 90 offset.y += 1;
87 } 91 }
88 if ((offset.x != 0) || (offset.y != 0)) { 92 if ((offset.x != 0) || (offset.y != 0)) {
@@ -91,17 +95,19 @@ static vec2 get_camera_movement(R dt) {
91 return offset; 95 return offset;
92} 96}
93 97
94static void update(GfxAppState* state, double t, double dt) { 98static void update(GfxApp* app, GfxAppState* state, double t, double dt) {
99 assert(app);
95 assert(state); 100 assert(state);
96 101
97 state->camera = vec2_add(state->camera, get_camera_movement((R)dt)); 102 state->camera = vec2_add(state->camera, get_camera_movement(app, (R)dt));
98 103
99 IsoGfx* iso = state->iso; 104 IsoGfx* iso = state->iso;
100 isogfx_set_camera(iso, (int)state->camera.x, (int)state->camera.y); 105 isogfx_set_camera(iso, (int)state->camera.x, (int)state->camera.y);
101 isogfx_update(iso, t); 106 isogfx_update(iso, t);
102} 107}
103 108
104static void render(GfxAppState* state) { 109static void render(const GfxApp* app, GfxAppState* state) {
110 assert(app);
105 assert(state); 111 assert(state);
106 112
107 IsoGfx* iso = state->iso; 113 IsoGfx* iso = state->iso;
@@ -109,7 +115,8 @@ static void render(GfxAppState* state) {
109 iso_backend_render(state->backend, iso); 115 iso_backend_render(state->backend, iso);
110} 116}
111 117
112static void resize(GfxAppState* state, int width, int height) { 118static void resize(GfxApp* app, GfxAppState* state, int width, int height) {
119 assert(app);
113 assert(state); 120 assert(state);
114 121
115 iso_backend_resize_window(state->backend, state->iso, width, height); 122 iso_backend_resize_window(state->backend, state->iso, width, height);
diff --git a/include/isogfx/asset.h b/include/isogfx/asset.h
index 9aeb55d..361ffcd 100644
--- a/include/isogfx/asset.h
+++ b/include/isogfx/asset.h
@@ -25,8 +25,15 @@ typedef struct Ts_Tile {
25 uint32_t pixels; // Byte offset into the Ts_TileSet's 'pixels'. 25 uint32_t pixels; // Byte offset into the Ts_TileSet's 'pixels'.
26} Ts_Tile; 26} Ts_Tile;
27 27
28// Tileset.
29//
30// Tile dimensions may be larger than the baseline tile dimensions of the
31// tileset if the tileset contains supertiles. Regardless, tile dimensions are
32// always a multiple of baseline dimensions along each axis.
28typedef struct Ts_TileSet { 33typedef struct Ts_TileSet {
29 uint16_t num_tiles; 34 uint16_t num_tiles;
35 uint16_t tile_width; // Baseline tile width.
36 uint16_t tile_height; // Baseline tile height.
30 uint16_t _pad; 37 uint16_t _pad;
31 Ts_Tile tiles[1]; // Count: num_tiles. 38 Ts_Tile tiles[1]; // Count: num_tiles.
32 Pixel pixels[]; // Count: sum_i(tile[i].width * tile[i].height). 39 Pixel pixels[]; // Count: sum_i(tile[i].width * tile[i].height).
@@ -36,6 +43,16 @@ typedef struct Ts_TileSet {
36// Tile map (TM) file format. 43// Tile map (TM) file format.
37// ----------------------------------------------------------------------------- 44// -----------------------------------------------------------------------------
38 45
46typedef enum Tm_Orientation {
47 Tm_Orthogonal = 0,
48 Tm_Isometric = 1,
49} Tm_Orientation;
50
51typedef struct Tm_Flags {
52 Tm_Orientation orientation : 1;
53 int unused : 15;
54} Tm_Flags;
55
39typedef struct Tm_Layer { 56typedef struct Tm_Layer {
40 Tile tiles[1]; // Count: world_width * world_height. 57 Tile tiles[1]; // Count: world_width * world_height.
41} Tm_Layer; 58} Tm_Layer;
@@ -47,6 +64,7 @@ typedef struct Tm_Map {
47 uint16_t base_tile_width; 64 uint16_t base_tile_width;
48 uint16_t base_tile_height; 65 uint16_t base_tile_height;
49 uint16_t num_layers; 66 uint16_t num_layers;
67 uint16_t flags; // Tm_flagsFlags
50 Tm_Layer layers[]; // Count: num_layers. 68 Tm_Layer layers[]; // Count: num_layers.
51} Tm_Map; 69} Tm_Map;
52 70
@@ -64,8 +82,8 @@ typedef struct Tm_Map {
64/// 82///
65/// Pixels are 8-bit indices into the sprite sheet's colour palette. 83/// Pixels are 8-bit indices into the sprite sheet's colour palette.
66typedef struct Ss_Row { 84typedef struct Ss_Row {
67 uint16_t num_cols; /// Number of columns in this row. 85 uint16_t num_cols; // Number of columns in this row.
68 uint8_t pixels[1]; /// Count: num_cols * sprite_width * sprite_height. 86 uint8_t pixels[1]; // Count: num_cols * sprite_width * sprite_height.
69} Ss_Row; 87} Ss_Row;
70 88
71typedef struct Ss_Palette { 89typedef struct Ss_Palette {
@@ -77,11 +95,11 @@ typedef struct Ss_Palette {
77/// 95///
78/// Sprite width and height are assumed constant throughout the sprite sheet. 96/// Sprite width and height are assumed constant throughout the sprite sheet.
79typedef struct Ss_SpriteSheet { 97typedef struct Ss_SpriteSheet {
80 uint16_t sprite_width; /// Sprite width in pixels. 98 uint16_t sprite_width; // Sprite width in pixels.
81 uint16_t sprite_height; /// Sprite height in pixels. 99 uint16_t sprite_height; // Sprite height in pixels.
82 uint16_t num_rows; 100 uint16_t num_rows;
83 Ss_Palette palette; /// Variable size. 101 Ss_Palette palette; // Variable size.
84 Ss_Row rows[1]; /// Count: num_rows. Variable offset. 102 Ss_Row rows[1]; // Count: num_rows. Variable offset.
85} Ss_SpriteSheet; 103} Ss_SpriteSheet;
86 104
87// ----------------------------------------------------------------------------- 105// -----------------------------------------------------------------------------
diff --git a/include/isogfx/isogfx.h b/include/isogfx/gfx2d.h
index e901231..323b389 100644
--- a/include/isogfx/isogfx.h
+++ b/include/isogfx/gfx2d.h
@@ -37,13 +37,13 @@ typedef struct TileDesc {
37 }; 37 };
38} TileDesc; 38} TileDesc;
39 39
40typedef struct WorldDesc { 40typedef struct MapDesc {
41 int tile_width; // Base tile width in pixels. 41 int tile_width; // Base tile width in pixels.
42 int tile_height; // Base tile height in pixels. 42 int tile_height; // Base tile height in pixels.
43 int world_width; // World width in tiles. 43 int world_width; // World width in tiles.
44 int world_height; // World height in tiles. 44 int world_height; // World height in tiles.
45 int num_tiles; // Number of tiles to allocate memory for. 45 int num_tiles; // Number of tiles to allocate memory for.
46} WorldDesc; 46} MapDesc;
47 47
48typedef struct IsoGfxDesc { 48typedef struct IsoGfxDesc {
49 void* memory; // Block of memory for the engine to use. 49 void* memory; // Block of memory for the engine to use.
@@ -61,11 +61,11 @@ void isogfx_del(IsoGfx**);
61/// Clear all loaded worlds and sprites. 61/// Clear all loaded worlds and sprites.
62void isogfx_clear(IsoGfx*); 62void isogfx_clear(IsoGfx*);
63 63
64/// Create an empty world. 64/// Create an empty map.
65void isogfx_make_world(IsoGfx*, const WorldDesc*); 65void isogfx_make_map(IsoGfx*, const MapDesc*);
66 66
67/// Load a world from a tile map (.TM) file. 67/// Load a tile map (.TM) file.
68bool isogfx_load_world(IsoGfx*, const char* filepath); 68bool isogfx_load_map(IsoGfx*, const char* filepath);
69 69
70/// Return the world's width. 70/// Return the world's width.
71int isogfx_world_width(const IsoGfx*); 71int isogfx_world_width(const IsoGfx*);
@@ -88,8 +88,10 @@ SpriteSheet isogfx_load_sprite_sheet(IsoGfx*, const char* filepath);
88/// Create an animated sprite. 88/// Create an animated sprite.
89Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet); 89Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet);
90 90
91/// Destroy all the sprites. 91// TODO: Add a function to delete a sprite. Making the caller manage and re-use
92void isogfx_del_sprites(IsoGfx*); 92// sprites is a shitty API.
93// Not that the stack allocator gets completely in the way; implement a free
94// list of sprites so that we can re-use the ones that have been "freed".
93 95
94/// Set the sprite's position. 96/// Set the sprite's position.
95void isogfx_set_sprite_position(IsoGfx*, Sprite, int x, int y); 97void isogfx_set_sprite_position(IsoGfx*, Sprite, int x, int y);
@@ -99,9 +101,13 @@ void isogfx_set_sprite_animation(IsoGfx*, Sprite, int animation);
99 101
100/// Update the renderer. 102/// Update the renderer.
101/// 103///
102/// Currently this updates the sprite animations. 104/// Currently, this updates the sprite animations.
103void isogfx_update(IsoGfx*, double t); 105void isogfx_update(IsoGfx*, double t);
104 106
107// TODO: Do we really need to store the camera in the library? It's not used
108// for anything other than to render, so we could remove library state and
109// take a camera argument in render() instead.
110
105/// Set the camera. 111/// Set the camera.
106void isogfx_set_camera(IsoGfx*, int x, int y); 112void isogfx_set_camera(IsoGfx*, int x, int y);
107 113
diff --git a/src/backend.c b/src/backend.c
index 94f1728..80c5974 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1,5 +1,5 @@
1#include <isogfx/backend.h> 1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h> 2#include <isogfx/gfx2d.h>
3 3
4#include <gfx/core.h> 4#include <gfx/core.h>
5#include <gfx/gfx.h> 5#include <gfx/gfx.h>
diff --git a/src/isogfx.c b/src/gfx2d.c
index c3a87bf..9767308 100644
--- a/src/isogfx.c
+++ b/src/gfx2d.c
@@ -1,4 +1,4 @@
1#include <isogfx/isogfx.h> 1#include <isogfx/gfx2d.h>
2 2
3#include <isogfx/asset.h> 3#include <isogfx/asset.h>
4 4
@@ -79,17 +79,49 @@ static inline ivec2 ivec2_scale(ivec2 a, int s) {
79 79
80static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; } 80static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; }
81 81
82static 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
87static inline vec2 vec2_add(vec2 a, vec2 b) { 82static inline vec2 vec2_add(vec2 a, vec2 b) {
88 return (vec2){.x = a.x + b.x, .y = a.y + b.y}; 83 return (vec2){.x = a.x + b.x, .y = a.y + b.y};
89} 84}
90 85
91static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } 86static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; }
92 87
88// Not actually used because we pre-compute the two axis vectors instead.
89// See make_iso_coord_system() and the other definition of iso2cart() below.
90// static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) {
91// return (ivec2){.x = (iso.x - iso.y) * (s / 2) + (w / 2),
92// .y = (iso.x + iso.y) * (t / 2)};
93// }
94
95/// Create the basis for the isometric coordinate system with origin and vectors
96/// expressed in the Cartesian system.
97static CoordSystem make_iso_coord_system(
98 const Tm_Map* const map, const Screen* const screen) {
99 assert(map);
100 assert(screen);
101 const ivec2 o = {screen->width / 2, 0};
102 const ivec2 x = {
103 .x = map->base_tile_width / 2, .y = map->base_tile_height / 2};
104 const ivec2 y = {
105 .x = -map->base_tile_width / 2, .y = map->base_tile_height / 2};
106 return (CoordSystem){o, x, y};
107}
108
109/// Map isometric coordinates to Cartesian coordinates.
110///
111/// For a tile, this gets the screen position of the top diamond-corner of the
112/// tile.
113///
114/// Takes the camera displacement into account.
115static ivec2 iso2cart(
116 const CoordSystem iso_space, ivec2 camera, int iso_x, int iso_y) {
117 const ivec2 vx_offset = ivec2_scale(iso_space.x, iso_x);
118 const ivec2 vy_offset = ivec2_scale(iso_space.y, iso_y);
119 const ivec2 screen_origin =
120 ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset));
121 const ivec2 origin_view_space = ivec2_add(screen_origin, ivec2_neg(camera));
122 return origin_view_space;
123}
124
93// Method 1. 125// Method 1.
94// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { 126// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
95// const double x = cart.x - (double)(w / 2); 127// const double x = cart.x - (double)(w / 2);
@@ -125,20 +157,6 @@ static inline Pixel* screen_xy_mut(Screen* screen, int x, int y) {
125 return (Pixel*)screen_xy_const_ref(screen, x, y); 157 return (Pixel*)screen_xy_const_ref(screen, x, y);
126} 158}
127 159
128/// Create the basis for the isometric coordinate system with origin and vectors
129/// expressed in the Cartesian system.
130static 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// ----------------------------------------------------------------------------- 160// -----------------------------------------------------------------------------
143// Renderer, world and tile management. 161// Renderer, world and tile management.
144// ----------------------------------------------------------------------------- 162// -----------------------------------------------------------------------------
@@ -198,7 +216,7 @@ void isogfx_del(IsoGfx** ppIso) {
198 } 216 }
199} 217}
200 218
201void isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { 219void isogfx_make_map(IsoGfx* iso, const MapDesc* desc) {
202 assert(iso); 220 assert(iso);
203 assert(desc); 221 assert(desc);
204 assert(desc->tile_width > 0); 222 assert(desc->tile_width > 0);
@@ -246,7 +264,7 @@ void isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) {
246 iso->iso_space = make_iso_coord_system(iso->map, &iso->screen); 264 iso->iso_space = make_iso_coord_system(iso->map, &iso->screen);
247} 265}
248 266
249bool isogfx_load_world(IsoGfx* iso, const char* filepath) { 267bool isogfx_load_map(IsoGfx* iso, const char* filepath) {
250 assert(iso); 268 assert(iso);
251 assert(filepath); 269 assert(filepath);
252 270
@@ -267,6 +285,8 @@ bool isogfx_load_world(IsoGfx* iso, const char* filepath) {
267 } 285 }
268 Tm_Map* const map = iso->map; 286 Tm_Map* const map = iso->map;
269 287
288 printf("Map orientation: %d\n", ((Tm_Flags*)&map->flags)->orientation);
289
270 // Load the tile set. 290 // Load the tile set.
271 // 291 //
272 // Tile set path is relative to the tile map file. Make it relative to the 292 // Tile set path is relative to the tile map file. Make it relative to the
@@ -496,18 +516,6 @@ void isogfx_update(IsoGfx* iso, double t) {
496// Rendering and picking. 516// Rendering and picking.
497// ----------------------------------------------------------------------------- 517// -----------------------------------------------------------------------------
498 518
499/// Get the screen position of the top diamond-corner of the tile at world
500/// (x,y).
501static ivec2 GetTileScreenOrigin(
502 const CoordSystem iso_space, ivec2 camera, int world_x, int world_y) {
503 const ivec2 vx_offset = ivec2_scale(iso_space.x, world_x);
504 const ivec2 vy_offset = ivec2_scale(iso_space.y, world_y);
505 const ivec2 screen_origin =
506 ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset));
507 const ivec2 origin_view_space = ivec2_add(screen_origin, ivec2_neg(camera));
508 return origin_view_space;
509}
510
511static Pixel alpha_blend(Pixel src, Pixel dst) { 519static Pixel alpha_blend(Pixel src, Pixel dst) {
512 if ((src.a == 255) || (dst.a == 0)) { 520 if ((src.a == 255) || (dst.a == 0)) {
513 return src; 521 return src;
@@ -590,7 +598,7 @@ static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) {
590 &iso->screen, top_left, pTile->width, pTile->height, pixels, nullptr); 598 &iso->screen, top_left, pTile->width, pTile->height, pixels, nullptr);
591} 599}
592 600
593static void draw_world(IsoGfx* iso) { 601static void draw_map(IsoGfx* iso) {
594 assert(iso); 602 assert(iso);
595 603
596 const int W = iso->screen.width; 604 const int W = iso->screen.width;
@@ -607,17 +615,15 @@ static void draw_world(IsoGfx* iso) {
607 // screen-centric approach would juggle multiple tiles throughout the scan. 615 // screen-centric approach would juggle multiple tiles throughout the scan.
608 for (int wy = 0; wy < iso->map->world_height; ++wy) { 616 for (int wy = 0; wy < iso->map->world_height; ++wy) {
609 for (int wx = 0; wx < iso->map->world_width; ++wx) { 617 for (int wx = 0; wx < iso->map->world_width; ++wx) {
610 const Tile tile = tm_layer_get_tile(iso->map, layer, wx, wy); 618 const Tile tile = tm_layer_get_tile(iso->map, layer, wx, wy);
611 const ivec2 screen_origin = 619 const ivec2 screen_origin = iso2cart(iso->iso_space, iso->camera, wx, wy);
612 GetTileScreenOrigin(iso->iso_space, iso->camera, wx, wy);
613 draw_tile(iso, screen_origin, tile); 620 draw_tile(iso, screen_origin, tile);
614 } 621 }
615 } 622 }
616} 623}
617 624
618static void draw_sprite( 625static void draw_sprite(
619 IsoGfx* iso, ivec2 origin, const SpriteInstance* sprite, 626 IsoGfx* iso, const SpriteInstance* sprite, const Ss_SpriteSheet* sheet) {
620 const Ss_SpriteSheet* sheet) {
621 assert(iso); 627 assert(iso);
622 assert(sprite); 628 assert(sprite);
623 assert(sheet); 629 assert(sheet);
@@ -625,10 +631,18 @@ static void draw_sprite(
625 assert(sprite->animation < sheet->num_rows); 631 assert(sprite->animation < sheet->num_rows);
626 assert(sprite->frame >= 0); 632 assert(sprite->frame >= 0);
627 633
634 // Apply an offset similarly to how we offset tiles. The sprite is offset by
635 // -base_tile_width/2 along the x-axis to align the sprite with the leftmost
636 // edge of the tile it is on.
637 const ivec2 screen_origin = iso2cart(
638 iso->iso_space, iso->camera, sprite->position.x, sprite->position.y);
639 const ivec2 offset = {-(iso->map->base_tile_width / 2), 0};
640 const ivec2 top_left = ivec2_add(screen_origin, offset);
641
628 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation); 642 const Ss_Row* row = ss_get_sprite_sheet_row(sheet, sprite->animation);
629 const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame); 643 const uint8_t* frame = ss_get_sprite_sheet_sprite(sheet, row, sprite->frame);
630 draw_rect( 644 draw_rect(
631 &iso->screen, origin, sheet->sprite_width, sheet->sprite_height, 645 &iso->screen, top_left, sheet->sprite_width, sheet->sprite_height,
632 sheet->palette.colours, frame); 646 sheet->palette.colours, frame);
633} 647}
634 648
@@ -639,10 +653,7 @@ static void draw_sprites(IsoGfx* iso) {
639 sprite = sprite->next) { 653 sprite = sprite->next) {
640 const Ss_SpriteSheet* sheet = sprite->sheet; 654 const Ss_SpriteSheet* sheet = sprite->sheet;
641 assert(sheet); 655 assert(sheet);
642 656 draw_sprite(iso, sprite, sheet);
643 const ivec2 screen_origin = GetTileScreenOrigin(
644 iso->iso_space, iso->camera, sprite->position.x, sprite->position.y);
645 draw_sprite(iso, screen_origin, sprite, sheet);
646 } 657 }
647} 658}
648 659
@@ -653,7 +664,7 @@ void isogfx_set_camera(IsoGfx* iso, int x, int y) {
653 664
654void isogfx_render(IsoGfx* iso) { 665void isogfx_render(IsoGfx* iso) {
655 assert(iso); 666 assert(iso);
656 draw_world(iso); 667 draw_map(iso);
657 draw_sprites(iso); 668 draw_sprites(iso);
658} 669}
659 670
@@ -664,8 +675,7 @@ void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) {
664 assert(x < iso->map->world_width); 675 assert(x < iso->map->world_width);
665 assert(y < iso->map->world_height); 676 assert(y < iso->map->world_height);
666 677
667 const ivec2 screen_origin = 678 const ivec2 screen_origin = iso2cart(iso->iso_space, iso->camera, x, y);
668 GetTileScreenOrigin(iso->iso_space, iso->camera, x, y);
669 draw_tile(iso, screen_origin, tile); 679 draw_tile(iso, screen_origin, tile);
670} 680}
671 681
diff --git a/tools/mkasset.py b/tools/mkasset.py
index ae27ead..f21a2f9 100644
--- a/tools/mkasset.py
+++ b/tools/mkasset.py
@@ -13,6 +13,8 @@
13import argparse 13import argparse
14import ctypes 14import ctypes
15import sys 15import sys
16from enum import IntEnum
17from typing import Generator
16from xml.etree import ElementTree 18from xml.etree import ElementTree
17 19
18from PIL import Image 20from PIL import Image
@@ -22,11 +24,17 @@ from PIL import Image
22MAX_PATH_LENGTH = 128 24MAX_PATH_LENGTH = 128
23 25
24 26
27class Orientation(IntEnum):
28 """Map orientation. Must match Tm_Orientation in asset.h"""
29 Orthogonal = 0
30 Isometric = 1
31
32
25def drop_extension(filepath): 33def drop_extension(filepath):
26 return filepath[:filepath.rfind('.')] 34 return filepath[:filepath.rfind('.')]
27 35
28 36
29def to_char_array(string, length): 37def to_char_array(string, length) -> bytes:
30 """Convert a string to a fixed-length ASCII char array. 38 """Convert a string to a fixed-length ASCII char array.
31 39
32 The length of str must be at most length-1 so that the resulting string can 40 The length of str must be at most length-1 so that the resulting string can
@@ -38,58 +46,129 @@ def to_char_array(string, length):
38 return chars + nulls 46 return chars + nulls
39 47
40 48
49def load_image(path) -> bytes:
50 """Load an image as an array of RGBA bytes."""
51 with Image.open(path) as im:
52 return im.convert('RGBA').tobytes()
53
54
55def carve_image(rgba_bytes, tile_width, tile_height, columns) -> Generator[bytearray]:
56 """Carve out individual tiles from a single-image tile set."""
57 # Image dimensions in pixels.
58 image_width = columns * tile_width
59 image_height = len(rgba_bytes) // image_width // 4
60
61 tile_bytes = bytearray(tile_width * tile_height * 4)
62 for image_y0 in range(0, image_height, tile_height): # y-origin of tile inside image
63 for image_x0 in range(0, image_width, tile_width): # x-origin of tile inside image
64 for y in range(tile_height):
65 image_y = image_y0 + y # y of current pixel inside image
66 for x in range(tile_width):
67 image_x = image_x0 + x # x of current pixel inside image
68 tile_bytes[(y * tile_width + x) * 4] = (
69 rgba_bytes)[(image_y * image_width + image_x) * 4]
70 yield tile_bytes.copy()
71
72
41def convert_tsx(input_filepath, output_filepath): 73def convert_tsx(input_filepath, output_filepath):
42 """Converts a Tiled .tsx tileset file to a .TS tile set file.""" 74 """Converts a Tiled .tsx tileset file to a .TS tile set file."""
43 xml = ElementTree.parse(input_filepath) 75 xml = ElementTree.parse(input_filepath)
44 root = xml.getroot() 76 tileset = xml.getroot()
77 assert (tileset.tag == "tileset")
45 78
46 tile_count = int(root.attrib["tilecount"]) 79 # Header.
47 max_tile_width = int(root.attrib["tilewidth"]) 80 tileset_tile_count = int(tileset.attrib["tilecount"])
48 max_tile_height = int(root.attrib["tileheight"]) 81 tileset_tile_width = int(tileset.attrib["tilewidth"])
82 tileset_tile_height = int(tileset.attrib["tileheight"])
49 83
50 print(f"Tile count: {tile_count}") 84 print(f"Tile count: {tileset_tile_count}")
51 print(f"Max width: {max_tile_width}") 85 print(f"Tile width: {tileset_tile_width}")
52 print(f"Max height: {max_tile_height}") 86 print(f"Tile height: {tileset_tile_height}")
53 87
54 pixels = [] # List of byte arrays 88 pixels = [] # List of byte arrays
55 pixels_offset = 0 89 pixels_offset = 0
56 90
91 def output_tile(output, tile_width, tile_height, tile_image_bytes):
92 # Expecting RGBA pixels.
93 assert (len(tile_image_bytes) == (tile_width * tile_height * 4))
94
95 nonlocal pixels_offset
96 tile_pixels_offset = pixels_offset
97 pixels.append(tile_image_bytes)
98 pixels_offset += len(tile_image_bytes)
99
100 output.write(ctypes.c_uint16(tile_width))
101 output.write(ctypes.c_uint16(tile_height))
102 output.write(ctypes.c_uint32(tile_pixels_offset))
103
57 with open(output_filepath, 'bw') as output: 104 with open(output_filepath, 'bw') as output:
58 # Write the header. 105 # Write the header.
59 output.write(ctypes.c_uint16(tile_count)) 106 output.write(ctypes.c_uint16(tileset_tile_count))
60 # output.write(ctypes.c_uint16(max_tile_width)) 107 output.write(ctypes.c_uint16(tileset_tile_width))
61 # output.write(ctypes.c_uint16(max_tile_height)) 108 output.write(ctypes.c_uint16(tileset_tile_height))
62 output.write(ctypes.c_uint16(0)) # Pad. 109 output.write(ctypes.c_uint16(0)) # Pad.
63 110
111 # A tileset made up of multiple images contains various 'tile' children,
112 # each with their own 'image':
113 #
114 # <tile id="0">
115 # <image width="32" height="32" source="separated images/tile_000.png"/>
116 # </tile>
117 # <tile id="1">
118 # <image width="32" height="32" source="separated images/tile_001.png"/>
119 # </tile>
120 #
121 # A tileset made up of a single image contains a single 'image' child:
122 #
123 # <image source="tileset.png" width="416" height="368"/>
64 num_tile = 0 124 num_tile = 0
65 for tile in root: 125 for child in tileset:
66 # Skip the "grid" and other non-tile elements. 126 if child.tag == "image":
67 if not tile.tag == "tile": 127 # This is a single-image tileset.
68 continue 128 image = child
69 129 image_path = image.attrib["source"]
70 # Assuming tiles are numbered 0..N. 130 image_bytes = load_image(image_path)
71 tile_id = int(tile.attrib["id"]) 131
72 assert (tile_id == num_tile) 132 # We expect the 'columns' attribute to be >0 for a single-image
73 num_tile += 1 133 # tile set.
74 134 columns = int(tileset.attrib["columns"])
75 image = tile[0] 135 assert (columns > 0)
76 tile_width = int(image.attrib["width"]) 136
77 tile_height = int(image.attrib["height"]) 137 # Tile dimensions are those given by the tileset's baseline width and height.
78 tile_path = image.attrib["source"] 138 tile_width = tileset_tile_width
79 139 tile_height = tileset_tile_height
80 assert (tile_width > 0) 140
81 assert (tile_height > 0) 141 # Cut each of the WxH tiles, and store them in the output in "tile order".
82 142 for tile_bytes in carve_image(image_bytes, tile_width, tile_height, columns):
83 tile_pixels_offset = pixels_offset 143 output_tile(output, tile_width, tile_height, tile_bytes)
84 with Image.open(tile_path) as im: 144
85 bytes = im.convert('RGBA').tobytes() 145 # Make sure not to process 'tile' elements, which may exist in
86 pixels.append(bytes) 146 # a single-image tileset to define probabilities, for example.
87 pixels_offset += len(bytes) 147 break
88 148
89 # Write the tile. 149 elif child.tag == "tile":
90 output.write(ctypes.c_uint16(tile_width)) 150 # This is a tile image in a tileset made of a collection of images.
91 output.write(ctypes.c_uint16(tile_height)) 151 tile = child
92 output.write(ctypes.c_uint32(tile_pixels_offset)) 152
153 # We assume that tiles are numbered 0..N-1. Assert this.
154 tile_id = int(tile.attrib["id"])
155 assert (tile_id == num_tile)
156 num_tile += 1
157
158 image = tile[0]
159 tile_width = int(image.attrib["width"])
160 tile_height = int(image.attrib["height"])
161 image_path = image.attrib["source"]
162
163 # Tile dimensions must be a multiple of the tileset's baseline
164 # tile width and height.
165 assert (tile_width > 0)
166 assert (tile_height > 0)
167 assert ((tile_width % tileset_tile_width) == 0)
168 assert ((tile_height % tileset_tile_height) == 0)
169
170 image_bytes = load_image(image_path)
171 output_tile(output, tile_width, tile_height, image_bytes)
93 172
94 # Write the pixel data. 173 # Write the pixel data.
95 for bytes in pixels: 174 for bytes in pixels:
@@ -106,11 +185,13 @@ def convert_tmx(input_filepath, output_filepath):
106 base_tile_width = int(root.attrib["tilewidth"]) 185 base_tile_width = int(root.attrib["tilewidth"])
107 base_tile_height = int(root.attrib["tileheight"]) 186 base_tile_height = int(root.attrib["tileheight"])
108 num_layers = 1 187 num_layers = 1
188 flags = Orientation.Isometric if (root.attrib["orientation"] == "isometric") else Orientation.Orthogonal
109 189
110 print(f"Map width: {map_width}") 190 print(f"Map width: {map_width}")
111 print(f"Map height: {map_height}") 191 print(f"Map height: {map_height}")
112 print(f"Tile width: {base_tile_width}") 192 print(f"Tile width: {base_tile_width}")
113 print(f"Tile height: {base_tile_height}") 193 print(f"Tile height: {base_tile_height}")
194 print(f"Orientation: {flags}")
114 195
115 tileset_path = None 196 tileset_path = None
116 197
@@ -133,6 +214,7 @@ def convert_tmx(input_filepath, output_filepath):
133 output.write(ctypes.c_uint16(base_tile_width)) 214 output.write(ctypes.c_uint16(base_tile_width))
134 output.write(ctypes.c_uint16(base_tile_height)) 215 output.write(ctypes.c_uint16(base_tile_height))
135 output.write(ctypes.c_uint16(num_layers)) 216 output.write(ctypes.c_uint16(num_layers))
217 output.write(ctypes.c_uint16(flags))
136 elif child.tag == "layer": 218 elif child.tag == "layer":
137 layer = child 219 layer = child
138 layer_id = int(layer.attrib["id"]) 220 layer_id = int(layer.attrib["id"])
@@ -272,23 +354,16 @@ def convert_sprite_sheet(input_file_paths, sprite_width, sprite_height,
272 # that. getcolors() returns the number of unique colors. 354 # that. getcolors() returns the number of unique colors.
273 # getpalette() also returns a flattened list, which is why we must *4. 355 # getpalette() also returns a flattened list, which is why we must *4.
274 num_colours = len(im.getcolors()) 356 num_colours = len(im.getcolors())
275 colours = im.getpalette(rawmode="RGBA")[:4 * num_colours] 357 palette = bytearray(im.getpalette(rawmode="RGBA")[:4 * num_colours])
276 palette = [] 358 assert (num_colours == (len(palette) // 4))
277 for i in range(0, 4 * num_colours, 4):
278 palette.append((colours[i], colours[i + 1], colours[i + 2],
279 colours[i + 3]))
280 359
281 output.write(ctypes.c_uint16(len(palette))) 360 output.write(ctypes.c_uint16(num_colours))
282 output.write(bytearray(colours)) 361 output.write(palette)
283 362
284 print(f"Sprite width: {sprite_width}") 363 print(f"Sprite width: {sprite_width}")
285 print(f"Sprite height: {sprite_height}") 364 print(f"Sprite height: {sprite_height}")
286 print(f"Rows: {len(rows)}") 365 print(f"Rows: {len(rows)}")
287 print(f"Colours: {len(palette)}") 366 print(f"Colours: {num_colours}")
288
289 # print("Palette")
290 # for i, colour in enumerate(palette):
291 # print(f"{i}: {colour}")
292 367
293 for row, num_columns in enumerate(rows): 368 for row, num_columns in enumerate(rows):
294 output.write(ctypes.c_uint16(num_columns)) 369 output.write(ctypes.c_uint16(num_columns))
@@ -310,36 +385,38 @@ def convert_sprite_sheet(input_file_paths, sprite_width, sprite_height,
310 385
311 386
312def main(): 387def main():
313 # TODO: Use subparser for each type of input file. 388 # TODO: Use a subparser for each type of input file for clarity of arguments.
314 parser = argparse.ArgumentParser() 389 parser = argparse.ArgumentParser()
315 parser.add_argument("input", 390 subparsers = parser.add_subparsers(dest="command")
316 nargs="+", 391 # Tile sets.
317 help="Input file (.tsx, .tmx) or path regex (sprite sheets)") 392 tileset_parser = subparsers.add_parser("tileset")
318 parser.add_argument("-W", "--width", type=int, help="Sprite width in pixels") 393 tileset_parser.add_argument("input", help="Input file (.tsx)")
319 parser.add_argument("-H", "--height", type=int, help="Sprite height in pixels") 394 # Tile maps.
320 parser.add_argument("-o", "--out", help="Output file (sprite sheets)") 395 tilemap_parser = subparsers.add_parser("tilemap")
396 tilemap_parser.add_argument("input", help="Input file (.tmx)")
397 # Sprite sheet.
398 sprite_parser = subparsers.add_parser("sprite")
399 sprite_parser.add_argument("input", nargs="+", help="Input files")
400 sprite_parser.add_argument("-W", "--width", type=int, required=True, help="Sprite width in pixels")
401 sprite_parser.add_argument("-H", "--height", type=int, required=True, help="Sprite height in pixels")
402 sprite_parser.add_argument("-o", "--out", help="Output file")
321 args = parser.parse_args() 403 args = parser.parse_args()
322 404
323 # TODO: Add support for TSX files made from a single image. Currently, only collections are supported. 405 if args.command == "tileset":
324 if ".tsx" in args.input[0]: 406 print(f"Processing: {args.input}")
325 args.input = args.input[0] # TODO: Remove this.
326 output_filepath_no_ext = drop_extension(args.input) 407 output_filepath_no_ext = drop_extension(args.input)
327 output_filepath = output_filepath_no_ext + ".ts" 408 output_filepath = output_filepath_no_ext + ".ts"
328 convert_tsx(args.input, output_filepath) 409 convert_tsx(args.input, output_filepath)
329 elif ".tmx" in args.input[0]: 410 elif args.command == "tilemap":
330 args.input = args.input[0] # TODO: Remove this. 411 print(f"Processing: {args.input}")
331 output_filepath_no_ext = drop_extension(args.input) 412 output_filepath_no_ext = drop_extension(args.input)
332 output_filepath = output_filepath_no_ext + ".tm" 413 output_filepath = output_filepath_no_ext + ".tm"
333 convert_tmx(args.input, output_filepath) 414 convert_tmx(args.input, output_filepath)
334 else: 415 elif args.command == "sprite":
335 # Sprite sheets. 416 print(f"Processing: {args.input}")
336 if not args.width or not args.height:
337 print("Sprite width and height must be given")
338 return 1
339 output_filepath = args.out if args.out else "out.ss" 417 output_filepath = args.out if args.out else "out.ss"
340 convert_sprite_sheet(args.input, args.width, args.height, 418 convert_sprite_sheet(args.input, args.width, args.height,
341 output_filepath) 419 output_filepath)
342
343 return 0 420 return 0
344 421
345 422