diff options
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | demos/checkerboard/CMakeLists.txt | 2 | ||||
-rw-r--r-- | demos/checkerboard/checkerboard.c | 32 | ||||
-rw-r--r-- | demos/isomap/CMakeLists.txt | 2 | ||||
-rw-r--r-- | demos/isomap/isomap.c | 41 | ||||
-rw-r--r-- | include/isogfx/asset.h | 30 | ||||
-rw-r--r-- | include/isogfx/gfx2d.h (renamed from include/isogfx/isogfx.h) | 24 | ||||
-rw-r--r-- | src/backend.c | 2 | ||||
-rw-r--r-- | src/gfx2d.c (renamed from src/isogfx.c) | 106 | ||||
-rw-r--r-- | tools/mkasset.py | 215 |
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 @@ | |||
1 | cmake_minimum_required(VERSION 3.0) | 1 | cmake_minimum_required(VERSION 3.5) |
2 | 2 | ||
3 | project(isogfx) | 3 | project(isogfx) |
4 | 4 | ||
@@ -10,7 +10,7 @@ set(CMAKE_C_EXTENSIONS Off) | |||
10 | 10 | ||
11 | add_library(isogfx | 11 | add_library(isogfx |
12 | src/asset.c | 12 | src/asset.c |
13 | src/isogfx.c) | 13 | src/gfx2d.c) |
14 | 14 | ||
15 | target_include_directories(isogfx PUBLIC | 15 | target_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 @@ | |||
1 | cmake_minimum_required(VERSION 3.0) | 1 | cmake_minimum_required(VERSION 3.5) |
2 | 2 | ||
3 | project(checkerboard) | 3 | project(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 | ||
69 | static bool init(GfxAppState* state, int argc, const char** argv) { | 69 | static 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 | ||
103 | static void shutdown(GfxAppState* state) { | 103 | static 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 | ||
110 | static void update(GfxAppState* state, double t, double dt) { | 111 | static 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 | ||
131 | static void render(GfxAppState* state) { | 133 | static 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 | ||
145 | static void resize(GfxAppState* state, int width, int height) { | 148 | static 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 @@ | |||
1 | cmake_minimum_required(VERSION 3.0) | 1 | cmake_minimum_required(VERSION 3.5) |
2 | 2 | ||
3 | project(isomap) | 3 | project(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; | |||
16 | static const int SCREEN_WIDTH = 704; | 16 | static const int SCREEN_WIDTH = 704; |
17 | static const int SCREEN_HEIGHT = 480; | 17 | static const int SCREEN_HEIGHT = 480; |
18 | 18 | ||
19 | static const R CAMERA_SPEED = 400; | 19 | static const R CAMERA_SPEED = 800; |
20 | 20 | ||
21 | #define MEMORY_SIZE (2 * 1024 * 1024) | 21 | #define MEMORY_SIZE (8 * 1024 * 1024) |
22 | uint8_t MEMORY[MEMORY_SIZE]; | 22 | uint8_t MEMORY[MEMORY_SIZE]; |
23 | 23 | ||
24 | typedef struct GfxAppState { | 24 | typedef struct GfxAppState { |
@@ -31,7 +31,8 @@ typedef struct GfxAppState { | |||
31 | Sprite stag; | 31 | Sprite stag; |
32 | } GfxAppState; | 32 | } GfxAppState; |
33 | 33 | ||
34 | static bool init(GfxAppState* state, int argc, const char** argv) { | 34 | static 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 | ||
69 | static void shutdown(GfxAppState* state) { | 71 | static void shutdown(GfxApp* app, GfxAppState* state) { |
72 | assert(app); | ||
70 | assert(state); | 73 | assert(state); |
71 | // | ||
72 | } | 74 | } |
73 | 75 | ||
74 | static vec2 get_camera_movement(R dt) { | 76 | static 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 | ||
94 | static void update(GfxAppState* state, double t, double dt) { | 98 | static 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 | ||
104 | static void render(GfxAppState* state) { | 109 | static 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 | ||
112 | static void resize(GfxAppState* state, int width, int height) { | 118 | static 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. | ||
28 | typedef struct Ts_TileSet { | 33 | typedef 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 | ||
46 | typedef enum Tm_Orientation { | ||
47 | Tm_Orthogonal = 0, | ||
48 | Tm_Isometric = 1, | ||
49 | } Tm_Orientation; | ||
50 | |||
51 | typedef struct Tm_Flags { | ||
52 | Tm_Orientation orientation : 1; | ||
53 | int unused : 15; | ||
54 | } Tm_Flags; | ||
55 | |||
39 | typedef struct Tm_Layer { | 56 | typedef 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. |
66 | typedef struct Ss_Row { | 84 | typedef 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 | ||
71 | typedef struct Ss_Palette { | 89 | typedef 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. |
79 | typedef struct Ss_SpriteSheet { | 97 | typedef 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 | ||
40 | typedef struct WorldDesc { | 40 | typedef 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 | ||
48 | typedef struct IsoGfxDesc { | 48 | typedef 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. |
62 | void isogfx_clear(IsoGfx*); | 62 | void isogfx_clear(IsoGfx*); |
63 | 63 | ||
64 | /// Create an empty world. | 64 | /// Create an empty map. |
65 | void isogfx_make_world(IsoGfx*, const WorldDesc*); | 65 | void 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. |
68 | bool isogfx_load_world(IsoGfx*, const char* filepath); | 68 | bool isogfx_load_map(IsoGfx*, const char* filepath); |
69 | 69 | ||
70 | /// Return the world's width. | 70 | /// Return the world's width. |
71 | int isogfx_world_width(const IsoGfx*); | 71 | int 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. |
89 | Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet); | 89 | Sprite 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 |
92 | void 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. |
95 | void isogfx_set_sprite_position(IsoGfx*, Sprite, int x, int y); | 97 | void 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. |
103 | void isogfx_update(IsoGfx*, double t); | 105 | void 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. |
106 | void isogfx_set_camera(IsoGfx*, int x, int y); | 112 | void 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 | ||
80 | static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; } | 80 | static inline ivec2 ivec2_neg(ivec2 a) { return (ivec2){.x = -a.x, .y = -a.y}; } |
81 | 81 | ||
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) { | 82 | static 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 | ||
91 | static inline vec2 ivec2_to_vec2(ivec2 a) { return (vec2){a.x, a.y}; } | 86 | static 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. | ||
97 | static 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. | ||
115 | static 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. | ||
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 | // ----------------------------------------------------------------------------- | 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 | ||
201 | void isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { | 219 | void 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 | ||
249 | bool isogfx_load_world(IsoGfx* iso, const char* filepath) { | 267 | bool 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). | ||
501 | static 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 | |||
511 | static Pixel alpha_blend(Pixel src, Pixel dst) { | 519 | static 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 | ||
593 | static void draw_world(IsoGfx* iso) { | 601 | static 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 | ||
618 | static void draw_sprite( | 625 | static 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 | ||
654 | void isogfx_render(IsoGfx* iso) { | 665 | void 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 @@ | |||
13 | import argparse | 13 | import argparse |
14 | import ctypes | 14 | import ctypes |
15 | import sys | 15 | import sys |
16 | from enum import IntEnum | ||
17 | from typing import Generator | ||
16 | from xml.etree import ElementTree | 18 | from xml.etree import ElementTree |
17 | 19 | ||
18 | from PIL import Image | 20 | from PIL import Image |
@@ -22,11 +24,17 @@ from PIL import Image | |||
22 | MAX_PATH_LENGTH = 128 | 24 | MAX_PATH_LENGTH = 128 |
23 | 25 | ||
24 | 26 | ||
27 | class Orientation(IntEnum): | ||
28 | """Map orientation. Must match Tm_Orientation in asset.h""" | ||
29 | Orthogonal = 0 | ||
30 | Isometric = 1 | ||
31 | |||
32 | |||
25 | def drop_extension(filepath): | 33 | def drop_extension(filepath): |
26 | return filepath[:filepath.rfind('.')] | 34 | return filepath[:filepath.rfind('.')] |
27 | 35 | ||
28 | 36 | ||
29 | def to_char_array(string, length): | 37 | def 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 | ||
49 | def 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 | |||
55 | def 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 | |||
41 | def convert_tsx(input_filepath, output_filepath): | 73 | def 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 | ||
312 | def main(): | 387 | def 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 | ||