diff options
author | 3gg <3gg@shellblade.net> | 2023-07-08 14:37:29 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2023-07-08 14:37:29 -0700 |
commit | 21a0d0c1c424f7db90c3282aad4bf6ad4ef809b7 (patch) | |
tree | a6ae8a8cb4108cd33713178e67d3b482fc1fd5ee | |
parent | 303f5dc58dd8e8266df3c62fc84d9799db8047b9 (diff) |
Load tile maps and tile sets from files.
-rw-r--r-- | gfx-iso/CMakeLists.txt | 11 | ||||
-rw-r--r-- | gfx-iso/app/app.h | 11 | ||||
-rw-r--r-- | gfx-iso/app/checkerboard.c | 120 | ||||
-rw-r--r-- | gfx-iso/app/checkerboard.h | 9 | ||||
-rw-r--r-- | gfx-iso/app/isogfx-demo.c | 67 | ||||
-rw-r--r-- | gfx-iso/app/isogfx-demo.h | 9 | ||||
-rw-r--r-- | gfx-iso/app/main.c (renamed from gfx-iso/demo/isogfx-demo.c) | 80 | ||||
-rw-r--r-- | gfx-iso/asset/mkasset.py | 155 | ||||
-rw-r--r-- | gfx-iso/include/isogfx/isogfx.h | 63 | ||||
-rw-r--r-- | gfx-iso/src/isogfx.c | 561 |
10 files changed, 853 insertions, 233 deletions
diff --git a/gfx-iso/CMakeLists.txt b/gfx-iso/CMakeLists.txt index 8f95f7f..b57a83f 100644 --- a/gfx-iso/CMakeLists.txt +++ b/gfx-iso/CMakeLists.txt | |||
@@ -13,18 +13,21 @@ target_include_directories(isogfx PUBLIC | |||
13 | include) | 13 | include) |
14 | 14 | ||
15 | target_link_libraries(isogfx PRIVATE | 15 | target_link_libraries(isogfx PRIVATE |
16 | filesystem | ||
16 | mempool) | 17 | mempool) |
17 | 18 | ||
18 | target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) | 19 | target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) |
19 | 20 | ||
20 | # Demo | 21 | # Demo |
21 | 22 | ||
22 | project(isogfx-demo) | 23 | project(isogfx-app) |
23 | 24 | ||
24 | add_executable(isogfx-demo | 25 | add_executable(isogfx-app |
25 | demo/isogfx-demo.c) | 26 | app/checkerboard.c |
27 | app/isogfx-demo.c | ||
28 | app/main.c) | ||
26 | 29 | ||
27 | target_link_libraries(isogfx-demo PRIVATE | 30 | target_link_libraries(isogfx-app PRIVATE |
28 | gfx | 31 | gfx |
29 | gfx-app | 32 | gfx-app |
30 | isogfx) | 33 | isogfx) |
diff --git a/gfx-iso/app/app.h b/gfx-iso/app/app.h new file mode 100644 index 0000000..160da47 --- /dev/null +++ b/gfx-iso/app/app.h | |||
@@ -0,0 +1,11 @@ | |||
1 | #pragma once | ||
2 | |||
3 | typedef struct IsoGfx IsoGfx; | ||
4 | typedef struct IsoGfxApp IsoGfxApp; | ||
5 | |||
6 | typedef struct IsoGfxApp { | ||
7 | void* state; | ||
8 | void (*shutdown)(IsoGfx*, void* state); | ||
9 | void (*update)(IsoGfx*, void* state, double t, double dt); | ||
10 | void (*render)(IsoGfx*, void* state); | ||
11 | } IsoGfxApp; | ||
diff --git a/gfx-iso/app/checkerboard.c b/gfx-iso/app/checkerboard.c new file mode 100644 index 0000000..8b394c4 --- /dev/null +++ b/gfx-iso/app/checkerboard.c | |||
@@ -0,0 +1,120 @@ | |||
1 | #include "isogfx-demo.h" | ||
2 | |||
3 | #include <gfx/gfx_app.h> | ||
4 | #include <isogfx/isogfx.h> | ||
5 | |||
6 | #include <assert.h> | ||
7 | #include <stdbool.h> | ||
8 | #include <stdio.h> | ||
9 | #include <stdlib.h> | ||
10 | |||
11 | static const int TILE_WIDTH = 64; | ||
12 | static const int TILE_HEIGHT = TILE_WIDTH / 2; | ||
13 | static const int WORLD_WIDTH = 20; | ||
14 | static const int WORLD_HEIGHT = 20; | ||
15 | |||
16 | static const TileDesc tile_set[] = { | ||
17 | {.type = TileFromColour, | ||
18 | .width = TILE_WIDTH, | ||
19 | .height = TILE_HEIGHT, | ||
20 | .colour = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46, .a = 0xff}}, | ||
21 | {.type = TileFromColour, | ||
22 | .width = TILE_WIDTH, | ||
23 | .height = TILE_HEIGHT, | ||
24 | .colour = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0, .a = 0xff}}, | ||
25 | {.type = TileFromColour, | ||
26 | .width = TILE_WIDTH, | ||
27 | .height = TILE_HEIGHT, | ||
28 | .colour = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84, .a = 0xff}}, | ||
29 | }; | ||
30 | |||
31 | typedef enum Colour { | ||
32 | Black, | ||
33 | White, | ||
34 | Red, | ||
35 | } Colour; | ||
36 | |||
37 | typedef struct State { | ||
38 | Tile red; | ||
39 | int xpick; | ||
40 | int ypick; | ||
41 | } State; | ||
42 | |||
43 | static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) { | ||
44 | assert(iso); | ||
45 | for (int y = 0; y < isogfx_world_height(iso); ++y) { | ||
46 | for (int x = 0; x < isogfx_world_width(iso); ++x) { | ||
47 | const int odd_col = x & 1; | ||
48 | const int odd_row = y & 1; | ||
49 | const Tile value = (odd_row ^ odd_col) == 0 ? black : white; | ||
50 | isogfx_set_tile(iso, x, y, value); | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | |||
55 | static void shutdown(IsoGfx* iso, void* app_state) { | ||
56 | assert(iso); | ||
57 | if (app_state) { | ||
58 | free(app_state); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | static void update(IsoGfx* iso, void* app_state, double t, double dt) { | ||
63 | assert(iso); | ||
64 | assert(app_state); | ||
65 | State* state = (State*)(app_state); | ||
66 | |||
67 | double mouse_x, mouse_y; | ||
68 | gfx_app_get_mouse_position(&mouse_x, &mouse_y); | ||
69 | |||
70 | isogfx_pick_tile(iso, mouse_x, mouse_y, &state->xpick, &state->ypick); | ||
71 | |||
72 | printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); | ||
73 | } | ||
74 | |||
75 | static void render(IsoGfx* iso, void* app_state) { | ||
76 | assert(iso); | ||
77 | assert(app_state); | ||
78 | State* state = (State*)(app_state); | ||
79 | |||
80 | isogfx_render(iso); | ||
81 | if ((state->xpick != -1) && (state->ypick != -1)) { | ||
82 | isogfx_draw_tile(iso, state->xpick, state->ypick, state->red); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | bool make_checkerboard_app(IsoGfx* iso, IsoGfxApp* app) { | ||
87 | assert(iso); | ||
88 | assert(app); | ||
89 | |||
90 | State* state = calloc(1, sizeof(State)); | ||
91 | if (!state) { | ||
92 | return false; | ||
93 | } | ||
94 | |||
95 | if (!isogfx_make_world( | ||
96 | iso, &(WorldDesc){ | ||
97 | .tile_width = TILE_WIDTH, | ||
98 | .tile_height = TILE_HEIGHT, | ||
99 | .world_width = WORLD_WIDTH, | ||
100 | .world_height = WORLD_HEIGHT})) { | ||
101 | goto cleanup; | ||
102 | } | ||
103 | |||
104 | const Tile black = isogfx_make_tile(iso, &tile_set[Black]); | ||
105 | const Tile white = isogfx_make_tile(iso, &tile_set[White]); | ||
106 | state->red = isogfx_make_tile(iso, &tile_set[Red]); | ||
107 | make_checkerboard(iso, black, white); | ||
108 | isogfx_render(iso); | ||
109 | |||
110 | app->state = state; | ||
111 | app->shutdown = shutdown; | ||
112 | app->update = update; | ||
113 | app->render = render; | ||
114 | |||
115 | return true; | ||
116 | |||
117 | cleanup: | ||
118 | free(state); | ||
119 | return false; | ||
120 | } | ||
diff --git a/gfx-iso/app/checkerboard.h b/gfx-iso/app/checkerboard.h new file mode 100644 index 0000000..61725a5 --- /dev/null +++ b/gfx-iso/app/checkerboard.h | |||
@@ -0,0 +1,9 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include "app.h" | ||
4 | |||
5 | #include <stdbool.h> | ||
6 | |||
7 | typedef struct IsoGfxApp IsoGfxApp; | ||
8 | |||
9 | bool make_checkerboard_app(IsoGfx*, IsoGfxApp*); | ||
diff --git a/gfx-iso/app/isogfx-demo.c b/gfx-iso/app/isogfx-demo.c new file mode 100644 index 0000000..15ab6be --- /dev/null +++ b/gfx-iso/app/isogfx-demo.c | |||
@@ -0,0 +1,67 @@ | |||
1 | #include "isogfx-demo.h" | ||
2 | |||
3 | #include <gfx/gfx_app.h> | ||
4 | #include <isogfx/isogfx.h> | ||
5 | |||
6 | #include <assert.h> | ||
7 | #include <stdbool.h> | ||
8 | #include <stdio.h> | ||
9 | #include <stdlib.h> | ||
10 | |||
11 | typedef struct State { | ||
12 | int xpick; | ||
13 | int ypick; | ||
14 | } State; | ||
15 | |||
16 | static void shutdown(IsoGfx* iso, void* app_state) { | ||
17 | assert(iso); | ||
18 | if (app_state) { | ||
19 | free(app_state); | ||
20 | } | ||
21 | } | ||
22 | |||
23 | static void update(IsoGfx* iso, void* app_state, double t, double dt) { | ||
24 | assert(iso); | ||
25 | assert(app_state); | ||
26 | State* state = (State*)(app_state); | ||
27 | |||
28 | double mouse_x, mouse_y; | ||
29 | gfx_app_get_mouse_position(&mouse_x, &mouse_y); | ||
30 | |||
31 | isogfx_pick_tile(iso, mouse_x, mouse_y, &state->xpick, &state->ypick); | ||
32 | |||
33 | // printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); | ||
34 | } | ||
35 | |||
36 | static void render(IsoGfx* iso, void* app_state) { | ||
37 | assert(iso); | ||
38 | assert(app_state); | ||
39 | State* state = (State*)(app_state); | ||
40 | |||
41 | isogfx_render(iso); | ||
42 | } | ||
43 | |||
44 | bool make_demo_app(IsoGfx* iso, IsoGfxApp* app) { | ||
45 | assert(iso); | ||
46 | assert(app); | ||
47 | |||
48 | State* state = calloc(1, sizeof(State)); | ||
49 | if (!state) { | ||
50 | return false; | ||
51 | } | ||
52 | |||
53 | if (!isogfx_load_world(iso, "/home/jeanne/assets/tilemaps/demo1.tm")) { | ||
54 | goto cleanup; | ||
55 | } | ||
56 | |||
57 | app->state = state; | ||
58 | app->shutdown = shutdown; | ||
59 | app->update = update; | ||
60 | app->render = render; | ||
61 | |||
62 | return true; | ||
63 | |||
64 | cleanup: | ||
65 | free(state); | ||
66 | return false; | ||
67 | } | ||
diff --git a/gfx-iso/app/isogfx-demo.h b/gfx-iso/app/isogfx-demo.h new file mode 100644 index 0000000..d099824 --- /dev/null +++ b/gfx-iso/app/isogfx-demo.h | |||
@@ -0,0 +1,9 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include "app.h" | ||
4 | |||
5 | #include <stdbool.h> | ||
6 | |||
7 | typedef struct IsoGfxApp IsoGfxApp; | ||
8 | |||
9 | bool make_demo_app(IsoGfx*, IsoGfxApp*); | ||
diff --git a/gfx-iso/demo/isogfx-demo.c b/gfx-iso/app/main.c index d6c1ab0..fa5a76b 100644 --- a/gfx-iso/demo/isogfx-demo.c +++ b/gfx-iso/app/main.c | |||
@@ -1,3 +1,7 @@ | |||
1 | #include "app.h" | ||
2 | #include "checkerboard.h" | ||
3 | #include "isogfx-demo.h" | ||
4 | |||
1 | #include <isogfx/isogfx.h> | 5 | #include <isogfx/isogfx.h> |
2 | 6 | ||
3 | #include <gfx/gfx.h> | 7 | #include <gfx/gfx.h> |
@@ -10,42 +14,19 @@ | |||
10 | 14 | ||
11 | #include <assert.h> | 15 | #include <assert.h> |
12 | #include <stdbool.h> | 16 | #include <stdbool.h> |
13 | #include <stdio.h> | ||
14 | #include <stdlib.h> | 17 | #include <stdlib.h> |
15 | 18 | ||
16 | static const int SCREEN_WIDTH = 1408; | 19 | static const int SCREEN_WIDTH = 1408; |
17 | static const int SCREEN_HEIGHT = 960; | 20 | static const int SCREEN_HEIGHT = 960; |
18 | static const int TILE_WIDTH = 64; | ||
19 | static const int TILE_HEIGHT = TILE_WIDTH / 2; | ||
20 | static const int WORLD_WIDTH = 20; | ||
21 | static const int WORLD_HEIGHT = 20; | ||
22 | |||
23 | static const Pixel BLACK = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46}; | ||
24 | static const Pixel WHITE = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0}; | ||
25 | static const Pixel RED = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84}; | ||
26 | 21 | ||
27 | typedef struct State { | 22 | typedef struct State { |
28 | Gfx* gfx; | 23 | Gfx* gfx; |
29 | IsoGfx* iso; | 24 | IsoGfx* iso; |
30 | Tile red; | 25 | IsoGfxApp app; |
31 | int xpick; | 26 | Texture* screen_texture; |
32 | int ypick; | 27 | Scene* scene; |
33 | Texture* screen_texture; | ||
34 | Scene* scene; | ||
35 | } State; | 28 | } State; |
36 | 29 | ||
37 | static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) { | ||
38 | assert(iso); | ||
39 | for (int y = 0; y < isogfx_world_height(iso); ++y) { | ||
40 | for (int x = 0; x < isogfx_world_width(iso); ++x) { | ||
41 | const int odd_col = x & 1; | ||
42 | const int odd_row = y & 1; | ||
43 | const Tile value = (odd_row ^ odd_col) == 0 ? black : white; | ||
44 | isogfx_set_tile(iso, x, y, value); | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | static bool init(const GfxAppDesc* desc, void** app_state) { | 30 | static bool init(const GfxAppDesc* desc, void** app_state) { |
50 | State* state = calloc(1, sizeof(State)); | 31 | State* state = calloc(1, sizeof(State)); |
51 | if (!state) { | 32 | if (!state) { |
@@ -53,12 +34,13 @@ static bool init(const GfxAppDesc* desc, void** app_state) { | |||
53 | } | 34 | } |
54 | 35 | ||
55 | if (!(state->iso = isogfx_new(&(IsoGfxDesc){ | 36 | if (!(state->iso = isogfx_new(&(IsoGfxDesc){ |
56 | .screen_width = SCREEN_WIDTH, | 37 | .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) { |
57 | .screen_height = SCREEN_HEIGHT, | 38 | goto cleanup; |
58 | .tile_width = TILE_WIDTH, | 39 | } |
59 | .tile_height = TILE_HEIGHT, | 40 | // if (!make_checkerboard_app(state->iso, &state->app)) { |
60 | .world_width = WORLD_WIDTH, | 41 | // goto cleanup; |
61 | .world_height = WORLD_HEIGHT}))) { | 42 | // } |
43 | if (!make_demo_app(state->iso, &state->app)) { | ||
62 | goto cleanup; | 44 | goto cleanup; |
63 | } | 45 | } |
64 | if (!(state->gfx = gfx_init())) { | 46 | if (!(state->gfx = gfx_init())) { |
@@ -71,7 +53,7 @@ static bool init(const GfxAppDesc* desc, void** app_state) { | |||
71 | .width = SCREEN_WIDTH, | 53 | .width = SCREEN_WIDTH, |
72 | .height = SCREEN_HEIGHT, | 54 | .height = SCREEN_HEIGHT, |
73 | .dimension = Texture2D, | 55 | .dimension = Texture2D, |
74 | .format = TextureRGB8, | 56 | .format = TextureSRGBA8, |
75 | .filtering = NearestFiltering, | 57 | .filtering = NearestFiltering, |
76 | .wrap = ClampToEdge, | 58 | .wrap = ClampToEdge, |
77 | .mipmaps = false}))) { | 59 | .mipmaps = false}))) { |
@@ -116,15 +98,6 @@ static bool init(const GfxAppDesc* desc, void** app_state) { | |||
116 | SceneNode* root = gfx_get_scene_root(state->scene); | 98 | SceneNode* root = gfx_get_scene_root(state->scene); |
117 | gfx_set_node_parent(node, root); | 99 | gfx_set_node_parent(node, root); |
118 | 100 | ||
119 | const Tile black = isogfx_make_tile( | ||
120 | state->iso, &(TileDesc){.type = TileFromColour, .colour = BLACK}); | ||
121 | const Tile white = isogfx_make_tile( | ||
122 | state->iso, &(TileDesc){.type = TileFromColour, .colour = WHITE}); | ||
123 | state->red = isogfx_make_tile( | ||
124 | state->iso, &(TileDesc){.type = TileFromColour, .colour = RED}); | ||
125 | make_checkerboard(state->iso, black, white); | ||
126 | isogfx_render(state->iso); | ||
127 | |||
128 | *app_state = state; | 101 | *app_state = state; |
129 | return true; | 102 | return true; |
130 | 103 | ||
@@ -139,6 +112,11 @@ cleanup: | |||
139 | static void shutdown(void* app_state) { | 112 | static void shutdown(void* app_state) { |
140 | assert(app_state); | 113 | assert(app_state); |
141 | State* state = (State*)(app_state); | 114 | State* state = (State*)(app_state); |
115 | |||
116 | if (state->app.state) { | ||
117 | assert(state->iso); | ||
118 | (*state->app.shutdown)(state->iso, state->app.state); | ||
119 | } | ||
142 | isogfx_del(&state->iso); | 120 | isogfx_del(&state->iso); |
143 | gfx_destroy(&state->gfx); | 121 | gfx_destroy(&state->gfx); |
144 | free(app_state); | 122 | free(app_state); |
@@ -148,22 +126,16 @@ static void update(void* app_state, double t, double dt) { | |||
148 | assert(app_state); | 126 | assert(app_state); |
149 | State* state = (State*)(app_state); | 127 | State* state = (State*)(app_state); |
150 | 128 | ||
151 | double mouse_x, mouse_y; | 129 | assert(state->app.update); |
152 | gfx_app_get_mouse_position(&mouse_x, &mouse_y); | 130 | (*state->app.update)(state->iso, state->app.state, t, dt); |
153 | |||
154 | isogfx_pick_tile(state->iso, mouse_x, mouse_y, &state->xpick, &state->ypick); | ||
155 | |||
156 | printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick); | ||
157 | } | 131 | } |
158 | 132 | ||
159 | static void render(void* app_state) { | 133 | static void render(void* app_state) { |
160 | assert(app_state); | 134 | assert(app_state); |
161 | State* state = (State*)(app_state); | 135 | State* state = (State*)(app_state); |
162 | 136 | ||
163 | isogfx_render(state->iso); | 137 | assert(state->app.render); |
164 | if ((state->xpick != -1) && (state->ypick != -1)) { | 138 | (*state->app.render)(state->iso, state->app.state); |
165 | isogfx_draw_tile(state->iso, state->xpick, state->ypick, state->red); | ||
166 | } | ||
167 | 139 | ||
168 | const Pixel* screen = isogfx_get_screen_buffer(state->iso); | 140 | const Pixel* screen = isogfx_get_screen_buffer(state->iso); |
169 | assert(screen); | 141 | assert(screen); |
diff --git a/gfx-iso/asset/mkasset.py b/gfx-iso/asset/mkasset.py new file mode 100644 index 0000000..15f7912 --- /dev/null +++ b/gfx-iso/asset/mkasset.py | |||
@@ -0,0 +1,155 @@ | |||
1 | # Converts tile sets and tile maps to binary formats (.TS, .TM) for the engine. | ||
2 | # | ||
3 | # Currently handles Tiled's .tsx and .tmx file formats. | ||
4 | # | ||
5 | # The output is a binary tile set file (.TS) or a binary tile map file (.TM). | ||
6 | import argparse | ||
7 | import ctypes | ||
8 | from PIL import Image | ||
9 | import sys | ||
10 | from xml.etree import ElementTree | ||
11 | |||
12 | # Maximum length of path strings in .TS and .TM files. | ||
13 | MAX_PATH_LENGTH = 128 | ||
14 | |||
15 | |||
16 | def drop_extension(filepath): | ||
17 | return filepath[:filepath.rfind('.')] | ||
18 | |||
19 | |||
20 | def to_char_array(string, length): | ||
21 | """Convert a string to a fixed-length ASCII char array. | ||
22 | |||
23 | The length of str must be at most length-1 so that the resulting string can | ||
24 | be null-terminated. | ||
25 | """ | ||
26 | assert (len(string) < length) | ||
27 | chars = string.encode("ascii") | ||
28 | nulls = ("\0" * (length - len(string))).encode("ascii") | ||
29 | return chars + nulls | ||
30 | |||
31 | |||
32 | def convert_tsx(input_filepath, output_filepath): | ||
33 | """Converts a Tiled .tsx tileset file to a .TS tile set file.""" | ||
34 | xml = ElementTree.parse(input_filepath) | ||
35 | root = xml.getroot() | ||
36 | |||
37 | tile_count = int(root.attrib["tilecount"]) | ||
38 | max_tile_width = int(root.attrib["tilewidth"]) | ||
39 | max_tile_height = int(root.attrib["tileheight"]) | ||
40 | |||
41 | print(f"Tile count: {tile_count}") | ||
42 | print(f"Max width: {max_tile_width}") | ||
43 | print(f"Max height: {max_tile_height}") | ||
44 | |||
45 | with open(output_filepath, 'bw') as output: | ||
46 | output.write(ctypes.c_uint16(tile_count)) | ||
47 | output.write(ctypes.c_uint16(max_tile_width)) | ||
48 | output.write(ctypes.c_uint16(max_tile_height)) | ||
49 | |||
50 | num_tile = 0 | ||
51 | for tile in root: | ||
52 | # Skip the "grid" and other non-tile elements. | ||
53 | if not tile.tag == "tile": | ||
54 | continue | ||
55 | |||
56 | # Assuming tiles are numbered 0..N. | ||
57 | tile_id = int(tile.attrib["id"]) | ||
58 | assert (tile_id == num_tile) | ||
59 | num_tile += 1 | ||
60 | |||
61 | image = tile[0] | ||
62 | tile_width = int(image.attrib["width"]) | ||
63 | tile_height = int(image.attrib["height"]) | ||
64 | tile_path = image.attrib["source"] | ||
65 | |||
66 | output.write(ctypes.c_uint16(tile_width)) | ||
67 | output.write(ctypes.c_uint16(tile_height)) | ||
68 | |||
69 | with Image.open(tile_path) as im: | ||
70 | bytes = im.convert('RGBA').tobytes() | ||
71 | output.write(bytes) | ||
72 | |||
73 | |||
74 | def convert_tmx(input_filepath, output_filepath): | ||
75 | """Converts a Tiled .tmx file to a .TM tile map file.""" | ||
76 | xml = ElementTree.parse(input_filepath) | ||
77 | root = xml.getroot() | ||
78 | |||
79 | map_width = int(root.attrib["width"]) | ||
80 | map_height = int(root.attrib["height"]) | ||
81 | base_tile_width = int(root.attrib["tilewidth"]) | ||
82 | base_tile_height = int(root.attrib["tileheight"]) | ||
83 | num_layers = 1 | ||
84 | |||
85 | print(f"Map width: {map_width}") | ||
86 | print(f"Map height: {map_height}") | ||
87 | print(f"Tile width: {base_tile_width}") | ||
88 | print(f"Tile height: {base_tile_height}") | ||
89 | |||
90 | with open(output_filepath, 'bw') as output: | ||
91 | output.write(ctypes.c_uint16(map_width)) | ||
92 | output.write(ctypes.c_uint16(map_height)) | ||
93 | output.write(ctypes.c_uint16(base_tile_width)) | ||
94 | output.write(ctypes.c_uint16(base_tile_height)) | ||
95 | output.write(ctypes.c_uint16(num_layers)) | ||
96 | |||
97 | tileset_path = None | ||
98 | |||
99 | for child in root: | ||
100 | if child.tag == "tileset": | ||
101 | tileset = child | ||
102 | tileset_path = tileset.attrib["source"] | ||
103 | |||
104 | print(f"Tile set: {tileset_path}") | ||
105 | |||
106 | tileset_path = tileset_path.replace("tsx", "ts") | ||
107 | elif child.tag == "layer": | ||
108 | layer = child | ||
109 | layer_id = int(layer.attrib["id"]) | ||
110 | layer_width = int(layer.attrib["width"]) | ||
111 | layer_height = int(layer.attrib["height"]) | ||
112 | |||
113 | print(f"Layer: {layer_id}") | ||
114 | print(f"Width: {layer_width}") | ||
115 | print(f"Height: {layer_height}") | ||
116 | |||
117 | assert (tileset_path) | ||
118 | output.write(to_char_array(tileset_path, MAX_PATH_LENGTH)) | ||
119 | |||
120 | # Assume the layer's dimensions matches the map's. | ||
121 | assert (layer_width == map_width) | ||
122 | assert (layer_height == map_height) | ||
123 | |||
124 | data = layer[0] | ||
125 | # Handle other encodings later. | ||
126 | assert (data.attrib["encoding"] == "csv") | ||
127 | |||
128 | csv = data.text.strip() | ||
129 | rows = csv.split('\n') | ||
130 | for row in rows: | ||
131 | tile_ids = [x.strip() for x in row.split(',') if x] | ||
132 | for tile_id in tile_ids: | ||
133 | output.write(ctypes.c_uint16(int(tile_id))) | ||
134 | |||
135 | |||
136 | def main(): | ||
137 | parser = argparse.ArgumentParser() | ||
138 | parser.add_argument("input", help="Input file (.tsx, .tmx)") | ||
139 | args = parser.parse_args() | ||
140 | |||
141 | output_filepath_no_ext = drop_extension(args.input) | ||
142 | if ".tsx" in args.input: | ||
143 | output_filepath = output_filepath_no_ext + ".ts" | ||
144 | convert_tsx(args.input, output_filepath) | ||
145 | elif ".tmx" in args.input: | ||
146 | output_filepath = output_filepath_no_ext + ".tm" | ||
147 | convert_tmx(args.input, output_filepath) | ||
148 | else: | ||
149 | print(f"Unhandled file format: {args.input}") | ||
150 | |||
151 | return 0 | ||
152 | |||
153 | |||
154 | if __name__ == '__main__': | ||
155 | sys.exit(main()) | ||
diff --git a/gfx-iso/include/isogfx/isogfx.h b/gfx-iso/include/isogfx/isogfx.h index a5f7770..22c8fd5 100644 --- a/gfx-iso/include/isogfx/isogfx.h +++ b/gfx-iso/include/isogfx/isogfx.h | |||
@@ -3,64 +3,97 @@ | |||
3 | */ | 3 | */ |
4 | #pragma once | 4 | #pragma once |
5 | 5 | ||
6 | #include <stdbool.h> | ||
6 | #include <stdint.h> | 7 | #include <stdint.h> |
7 | 8 | ||
8 | typedef struct IsoGfx IsoGfx; | 9 | typedef struct IsoGfx IsoGfx; |
9 | 10 | ||
10 | typedef uint8_t Tile; | 11 | /// Tile handle. |
12 | typedef uint16_t Tile; | ||
13 | |||
14 | /// Colour channel. | ||
11 | typedef uint8_t Channel; | 15 | typedef uint8_t Channel; |
12 | 16 | ||
13 | typedef struct Pixel { | 17 | typedef struct Pixel { |
14 | Channel r, g, b; | 18 | Channel r, g, b, a; |
15 | } Pixel; | 19 | } Pixel; |
16 | 20 | ||
17 | typedef enum TileDescType { | 21 | typedef enum TileDescType { |
18 | TileFromColour, | 22 | TileFromColour, |
19 | TileFromFile, | 23 | TileFromFile, |
20 | TileFromMemory | 24 | TileFromMemory, |
21 | } TileDescType; | 25 | } TileDescType; |
22 | 26 | ||
23 | typedef struct TileDesc { | 27 | typedef struct TileDesc { |
24 | TileDescType type; | 28 | TileDescType type; |
29 | int width; /// Tile width in pixels. | ||
30 | int height; /// Tile height in pixels. | ||
25 | union { | 31 | union { |
26 | Pixel colour; | 32 | Pixel colour; /// Constant colour tile. |
27 | struct { | 33 | struct { |
28 | const char* path; | 34 | const char* path; |
29 | } file; | 35 | } file; |
30 | struct { | 36 | struct { |
31 | const void* data; | 37 | const uint8_t* data; /// sizeof(Pixel) * width * height |
32 | } mem; | 38 | } mem; |
33 | }; | 39 | }; |
34 | } TileDesc; | 40 | } TileDesc; |
35 | 41 | ||
42 | typedef struct WorldDesc { | ||
43 | int tile_width; /// Base tile width in pixels. | ||
44 | int tile_height; /// Base tile height in pixels. | ||
45 | int world_width; /// World width in tiles. | ||
46 | int world_height; /// World height in tiles. | ||
47 | int max_num_tiles; /// 0 for an implementation-defined default. | ||
48 | } WorldDesc; | ||
49 | |||
36 | typedef struct IsoGfxDesc { | 50 | typedef struct IsoGfxDesc { |
37 | int screen_width; | 51 | int screen_width; /// Screen width in pixels. |
38 | int screen_height; | 52 | int screen_height; /// Screen height in pixels. |
39 | int tile_width; | ||
40 | int tile_height; | ||
41 | int world_width; | ||
42 | int world_height; | ||
43 | int max_num_tiles; // 0 for an implementation-defined default. | ||
44 | } IsoGfxDesc; | 53 | } IsoGfxDesc; |
45 | 54 | ||
55 | /// Create a new isometric graphics engine. | ||
46 | IsoGfx* isogfx_new(const IsoGfxDesc*); | 56 | IsoGfx* isogfx_new(const IsoGfxDesc*); |
47 | 57 | ||
58 | /// Destroy the isometric graphics engine. | ||
48 | void isogfx_del(IsoGfx**); | 59 | void isogfx_del(IsoGfx**); |
49 | 60 | ||
61 | /// Create an empty world. | ||
62 | bool isogfx_make_world(IsoGfx*, const WorldDesc*); | ||
63 | |||
64 | /// Load a world from a tile map (.TM) file. | ||
65 | bool isogfx_load_world(IsoGfx*, const char* filepath); | ||
66 | |||
67 | /// Return the world's width. | ||
68 | int isogfx_world_width(const IsoGfx*); | ||
69 | |||
70 | /// Return the world's height. | ||
71 | int isogfx_world_height(const IsoGfx*); | ||
72 | |||
73 | /// Create a new tile. | ||
50 | Tile isogfx_make_tile(IsoGfx*, const TileDesc*); | 74 | Tile isogfx_make_tile(IsoGfx*, const TileDesc*); |
51 | 75 | ||
76 | /// Set the tile at position (x,y). | ||
52 | void isogfx_set_tile(IsoGfx*, int x, int y, Tile); | 77 | void isogfx_set_tile(IsoGfx*, int x, int y, Tile); |
53 | 78 | ||
79 | /// Set the tiles in positions in the range (x0,y0) - (x1,y1). | ||
54 | void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); | 80 | void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); |
55 | 81 | ||
82 | /// Translate Cartesian to isometric coordinates. | ||
56 | void isogfx_pick_tile( | 83 | void isogfx_pick_tile( |
57 | const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso); | 84 | const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso); |
58 | 85 | ||
86 | /// Render the world. | ||
59 | void isogfx_render(IsoGfx*); | 87 | void isogfx_render(IsoGfx*); |
60 | 88 | ||
89 | /// Draw/overlay a tile at position (x,y). | ||
90 | /// | ||
91 | /// This function just renders a tile at position (x,y) and should be called | ||
92 | /// after isogfx_render() to obtain the correct result. To set the tile at | ||
93 | /// position (x,y) instead, use isogfx_set_tile(). | ||
61 | void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); | 94 | void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); |
62 | 95 | ||
96 | /// Return a pointer to the internal colour buffer. | ||
97 | /// | ||
98 | /// Call after each call to isogfx_render() to retrieve the render output. | ||
63 | const Pixel* isogfx_get_screen_buffer(const IsoGfx*); | 99 | const Pixel* isogfx_get_screen_buffer(const IsoGfx*); |
64 | |||
65 | int isogfx_world_width(const IsoGfx*); | ||
66 | int isogfx_world_height(const IsoGfx*); | ||
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c index b38efe7..17b88b2 100644 --- a/gfx-iso/src/isogfx.c +++ b/gfx-iso/src/isogfx.c | |||
@@ -1,36 +1,106 @@ | |||
1 | #include <isogfx/isogfx.h> | 1 | #include <isogfx/isogfx.h> |
2 | 2 | ||
3 | #include <filesystem.h> | ||
3 | #include <mempool.h> | 4 | #include <mempool.h> |
4 | 5 | ||
6 | #include <linux/limits.h> | ||
7 | |||
5 | #include <assert.h> | 8 | #include <assert.h> |
6 | #include <stdbool.h> | 9 | #include <stdbool.h> |
7 | #include <stdint.h> | 10 | #include <stdint.h> |
11 | #include <stdio.h> | ||
8 | #include <stdlib.h> | 12 | #include <stdlib.h> |
9 | #include <string.h> | 13 | #include <string.h> |
10 | 14 | ||
11 | /// Maximum number of tiles unless the user chooses a non-zero value. | 15 | /// Maximum number of tiles unless the user chooses a non-zero value. |
12 | #define DEFAULT_MAX_NUM_TILES 1024 | 16 | #define DEFAULT_MAX_NUM_TILES 1024 |
13 | 17 | ||
18 | // ----------------------------------------------------------------------------- | ||
19 | // Tile set (TS) and tile map (TM) file formats. | ||
20 | // ----------------------------------------------------------------------------- | ||
21 | |||
22 | /// Maximum length of path strings in .TS and .TM files. | ||
23 | #define MAX_PATH_LENGTH 128 | ||
24 | |||
25 | typedef struct Ts_Tile { | ||
26 | uint16_t width; /// Tile width in pixels. | ||
27 | uint16_t height; /// Tile height in pixels. | ||
28 | Pixel pixels[1]; /// Count: width * height. | ||
29 | } Ts_Tile; | ||
30 | |||
31 | typedef struct Ts_TileSet { | ||
32 | uint16_t num_tiles; | ||
33 | uint16_t max_tile_width; /// Maximum tile width in pixels. | ||
34 | uint16_t max_tile_height; /// Maximum tile height in pixels. | ||
35 | Ts_Tile tiles[1]; /// Count: num_tiles. | ||
36 | } Ts_TileSet; | ||
37 | |||
38 | typedef struct Tm_Layer { | ||
39 | union { | ||
40 | char tileset_path[MAX_PATH_LENGTH]; // Relative to the Tm_Map file. | ||
41 | }; | ||
42 | Tile tiles[1]; /// Count: world_width * world_height. | ||
43 | } Tm_Layer; | ||
44 | |||
45 | typedef struct Tm_Map { | ||
46 | uint16_t world_width; /// World width in number of tiles. | ||
47 | uint16_t world_height; /// World height in number of tiles. | ||
48 | uint16_t base_tile_width; | ||
49 | uint16_t base_tile_height; | ||
50 | uint16_t num_layers; | ||
51 | Tm_Layer layers[1]; // Count: num_layers. | ||
52 | } Tm_Map; | ||
53 | |||
54 | static inline const Tm_Layer* tm_map_get_next_layer( | ||
55 | const Tm_Map* map, const Tm_Layer* layer) { | ||
56 | assert(map); | ||
57 | assert(layer); | ||
58 | return (const Tm_Layer*)((const uint8_t*)layer + sizeof(Tm_Layer) + | ||
59 | ((map->world_width * map->world_height - 1) * | ||
60 | sizeof(Tile))); | ||
61 | } | ||
62 | |||
63 | static inline const Ts_Tile* ts_tileset_get_next_tile( | ||
64 | const Ts_TileSet* tileset, const Ts_Tile* tile) { | ||
65 | assert(tileset); | ||
66 | assert(tile); | ||
67 | return (const Ts_Tile*)((const uint8_t*)tile + sizeof(Ts_Tile) + | ||
68 | ((tile->width * tile->height - 1) * sizeof(Pixel))); | ||
69 | } | ||
70 | |||
71 | // ----------------------------------------------------------------------------- | ||
72 | // Renderer state. | ||
73 | // ----------------------------------------------------------------------------- | ||
74 | |||
75 | // typedef Ts_Tile TileData; | ||
76 | |||
14 | typedef struct TileData { | 77 | typedef struct TileData { |
15 | Pixel pixels[1]; // Dynamically allocated. | 78 | uint16_t width; |
79 | uint16_t height; | ||
80 | uint16_t num_blocks; // Number of pixel blocks in the pixels mempool. | ||
81 | uint16_t pixels_index; // Offset into the pixels mempool. | ||
16 | } TileData; | 82 | } TileData; |
17 | 83 | ||
18 | DEF_MEMPOOL_DYN(TilePool, TileData) | 84 | DEF_MEMPOOL_DYN(TilePool, TileData) |
85 | DEF_MEMPOOL_DYN(PixelPool, Pixel) | ||
19 | 86 | ||
20 | typedef struct IsoGfx { | 87 | typedef struct IsoGfx { |
21 | Tile* world; | 88 | int screen_width; |
22 | Pixel* screen; | 89 | int screen_height; |
23 | uint8_t* tile_mask; | 90 | int tile_width; |
24 | TilePool tiles; | 91 | int tile_height; |
25 | int screen_width; | 92 | int world_width; |
26 | int screen_height; | 93 | int world_height; |
27 | int tile_width; | 94 | Tile* world; |
28 | int tile_height; | 95 | Pixel* screen; |
29 | int world_width; | 96 | TilePool tiles; |
30 | int world_height; | 97 | PixelPool pixels; |
31 | int max_num_tiles; | ||
32 | } IsoGfx; | 98 | } IsoGfx; |
33 | 99 | ||
100 | // ----------------------------------------------------------------------------- | ||
101 | // Math and world / tile / screen access. | ||
102 | // ----------------------------------------------------------------------------- | ||
103 | |||
34 | typedef struct ivec2 { | 104 | typedef struct ivec2 { |
35 | int x, y; | 105 | int x, y; |
36 | } ivec2; | 106 | } ivec2; |
@@ -70,38 +140,27 @@ static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { | |||
70 | .y = (-one_over_s * x + one_over_t * cart.y)}; | 140 | .y = (-one_over_s * x + one_over_t * cart.y)}; |
71 | } | 141 | } |
72 | 142 | ||
73 | Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { | 143 | static const Pixel* tile_xy_const_ref( |
144 | const IsoGfx* iso, const TileData* tile, int x, int y) { | ||
74 | assert(iso); | 145 | assert(iso); |
75 | assert(tile); | 146 | assert(tile); |
76 | assert(tile->pixels); | ||
77 | assert(x >= 0); | 147 | assert(x >= 0); |
78 | assert(y >= 0); | 148 | assert(y >= 0); |
79 | assert(x < iso->tile_width); | 149 | assert(x < tile->width); |
80 | assert(y < iso->tile_height); | 150 | assert(y < tile->height); |
81 | return &tile->pixels[y * iso->tile_width + x]; | 151 | return &mempool_get_block( |
152 | &iso->pixels, tile->pixels_index)[y * tile->width + x]; | ||
82 | } | 153 | } |
83 | 154 | ||
84 | Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { | 155 | static Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { |
85 | assert(iso); | 156 | return *tile_xy_const_ref(iso, tile, x, y); |
86 | assert(tile); | ||
87 | assert(tile->pixels); | ||
88 | assert(x >= 0); | ||
89 | assert(y >= 0); | ||
90 | assert(x < iso->tile_width); | ||
91 | assert(y < iso->tile_height); | ||
92 | return tile->pixels[y * iso->tile_width + x]; | ||
93 | } | 157 | } |
94 | 158 | ||
95 | static inline Tile world_xy(IsoGfx* iso, int x, int y) { | 159 | static Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { |
96 | assert(iso); | 160 | return (Pixel*)tile_xy_const_ref(iso, tile, x, y); |
97 | assert(x >= 0); | ||
98 | assert(y >= 0); | ||
99 | assert(x < iso->world_width); | ||
100 | assert(y < iso->world_height); | ||
101 | return iso->world[y * iso->world_width + x]; | ||
102 | } | 161 | } |
103 | 162 | ||
104 | static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { | 163 | static inline const Tile* world_xy_const_ref(const IsoGfx* iso, int x, int y) { |
105 | assert(iso); | 164 | assert(iso); |
106 | assert(x >= 0); | 165 | assert(x >= 0); |
107 | assert(y >= 0); | 166 | assert(y >= 0); |
@@ -110,16 +169,16 @@ static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { | |||
110 | return &iso->world[y * iso->world_width + x]; | 169 | return &iso->world[y * iso->world_width + x]; |
111 | } | 170 | } |
112 | 171 | ||
113 | static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { | 172 | static inline Tile world_xy(const IsoGfx* iso, int x, int y) { |
114 | assert(iso); | 173 | return *world_xy_const_ref(iso, x, y); |
115 | assert(x >= 0); | ||
116 | assert(y >= 0); | ||
117 | assert(x < iso->screen_width); | ||
118 | assert(y < iso->screen_height); | ||
119 | return iso->screen[y * iso->screen_width + x]; | ||
120 | } | 174 | } |
121 | 175 | ||
122 | static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { | 176 | static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { |
177 | return (Tile*)world_xy_const_ref(iso, x, y); | ||
178 | } | ||
179 | |||
180 | static inline const Pixel* screen_xy_const_ref( | ||
181 | const IsoGfx* iso, int x, int y) { | ||
123 | assert(iso); | 182 | assert(iso); |
124 | assert(x >= 0); | 183 | assert(x >= 0); |
125 | assert(y >= 0); | 184 | assert(y >= 0); |
@@ -128,169 +187,279 @@ static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { | |||
128 | return &iso->screen[y * iso->screen_width + x]; | 187 | return &iso->screen[y * iso->screen_width + x]; |
129 | } | 188 | } |
130 | 189 | ||
131 | static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { | 190 | static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { |
132 | assert(iso); | 191 | return *screen_xy_const_ref(iso, x, y); |
133 | |||
134 | const TileData* data = mempool_get_block(&iso->tiles, tile); | ||
135 | assert(data); | ||
136 | |||
137 | for (int py = 0; py < iso->tile_height; ++py) { | ||
138 | for (int px = 0; px < iso->tile_width; ++px) { | ||
139 | const Pixel colour = tile_xy(iso, data, px, py); | ||
140 | const int sx = origin.x + px; | ||
141 | const int sy = origin.y + py; | ||
142 | if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) && | ||
143 | (sy < iso->screen_height)) { | ||
144 | const uint8_t mask = iso->tile_mask[py * iso->tile_width + px]; | ||
145 | if (mask == 1) { | ||
146 | *screen_xy_mut(iso, sx, sy) = colour; | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | } | 192 | } |
152 | 193 | ||
153 | static void draw(IsoGfx* iso) { | 194 | static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { |
154 | assert(iso); | 195 | return (Pixel*)screen_xy_const_ref(iso, x, y); |
155 | 196 | } | |
156 | const int W = iso->screen_width; | ||
157 | const int H = iso->screen_height; | ||
158 | 197 | ||
159 | memset(iso->screen, 0, W * H * sizeof(Pixel)); | 198 | // ----------------------------------------------------------------------------- |
199 | // Renderer, world and tile management. | ||
200 | // ----------------------------------------------------------------------------- | ||
160 | 201 | ||
161 | const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; | 202 | IsoGfx* isogfx_new(const IsoGfxDesc* desc) { |
162 | const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; | 203 | assert(desc->screen_width > 0); |
163 | const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; | 204 | assert(desc->screen_height > 0); |
205 | // Part of our implementation assumes even widths and heights for precision. | ||
206 | assert((desc->screen_width & 1) == 0); | ||
207 | assert((desc->screen_height & 1) == 0); | ||
164 | 208 | ||
165 | // TODO: Culling. | 209 | IsoGfx* iso = calloc(1, sizeof(IsoGfx)); |
166 | // Ex: map the screen corners to tile space to cull. | 210 | if (!iso) { |
167 | // Ex: walk in screen space and fetch the tile. | 211 | return 0; |
168 | // The tile-centric approach might be more cache-friendly, however, since the | ||
169 | // screen-centric approach would juggle multiple tiles throughout the scan. | ||
170 | for (int ty = 0; ty < iso->world_height; ++ty) { | ||
171 | for (int tx = 0; tx < iso->world_width; ++tx) { | ||
172 | const Tile tile = world_xy(iso, tx, ty); | ||
173 | const ivec2 so = | ||
174 | ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty))); | ||
175 | draw_tile(iso, so, tile); | ||
176 | } | ||
177 | } | 212 | } |
178 | } | ||
179 | |||
180 | /// Creates a tile mask procedurally. | ||
181 | static void make_tile_mask(IsoGfx* iso) { | ||
182 | assert(iso); | ||
183 | assert(iso->tile_mask); | ||
184 | 213 | ||
185 | for (int y = 0; y < iso->tile_height / 2; ++y) { | 214 | iso->screen_width = desc->screen_width; |
186 | const int mask_start = iso->tile_width / 2 - 2 * y - 1; | 215 | iso->screen_height = desc->screen_height; |
187 | const int mask_end = iso->tile_width / 2 + 2 * y + 1; | ||
188 | for (int x = 0; x < iso->tile_width; ++x) { | ||
189 | const bool masked = (mask_start <= x) && (x <= mask_end); | ||
190 | const uint8_t val = masked ? 1 : 0; | ||
191 | 216 | ||
192 | // Top half. | 217 | const int screen_size = desc->screen_width * desc->screen_height; |
193 | iso->tile_mask[y * iso->tile_width + x] = val; | ||
194 | 218 | ||
195 | // Bottom half reflects the top half. | 219 | if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { |
196 | const int y_reflected = iso->tile_height - y - 1; | 220 | goto cleanup; |
197 | iso->tile_mask[y_reflected * iso->tile_width + x] = val; | ||
198 | } | ||
199 | } | 221 | } |
222 | |||
223 | return iso; | ||
224 | |||
225 | cleanup: | ||
226 | isogfx_del(&iso); | ||
227 | return 0; | ||
200 | } | 228 | } |
201 | 229 | ||
202 | /// Creates a tile with a constant colour. | 230 | /// Destroy the world and its tile set. |
203 | static void make_tile_from_colour( | 231 | static void destroy_world(IsoGfx* iso) { |
204 | const IsoGfx* iso, Pixel colour, TileData* tile) { | ||
205 | assert(iso); | 232 | assert(iso); |
206 | assert(tile); | 233 | if (iso->world) { |
234 | free(iso->world); | ||
235 | iso->world = 0; | ||
236 | } | ||
237 | mempool_del(&iso->tiles); | ||
238 | mempool_del(&iso->pixels); | ||
239 | } | ||
207 | 240 | ||
208 | for (int y = 0; y < iso->tile_height; ++y) { | 241 | void isogfx_del(IsoGfx** pIso) { |
209 | for (int x = 0; x < iso->tile_width; ++x) { | 242 | assert(pIso); |
210 | *tile_xy_mut(iso, tile, x, y) = colour; | 243 | IsoGfx* iso = *pIso; |
244 | if (iso) { | ||
245 | destroy_world(iso); | ||
246 | if (iso->screen) { | ||
247 | free(iso->screen); | ||
248 | iso->screen = 0; | ||
211 | } | 249 | } |
250 | free(iso); | ||
251 | *pIso = 0; | ||
212 | } | 252 | } |
213 | } | 253 | } |
214 | 254 | ||
215 | IsoGfx* isogfx_new(const IsoGfxDesc* desc) { | 255 | bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) { |
216 | assert(desc->screen_width > 0); | 256 | assert(iso); |
217 | assert(desc->screen_height > 0); | 257 | assert(desc); |
218 | assert(desc->tile_width > 0); | 258 | assert(desc->tile_width > 0); |
219 | assert(desc->tile_height > 0); | 259 | assert(desc->tile_height > 0); |
220 | // Part of our implementation assumes even widths and heights for greater | 260 | // Part of our implementation assumes even widths and heights for greater |
221 | // precision. | 261 | // precision. |
222 | assert((desc->screen_width & 1) == 0); | ||
223 | assert((desc->screen_height & 1) == 0); | ||
224 | assert((desc->tile_width & 1) == 0); | 262 | assert((desc->tile_width & 1) == 0); |
225 | assert((desc->tile_height & 1) == 0); | 263 | assert((desc->tile_height & 1) == 0); |
226 | 264 | ||
227 | IsoGfx* iso = calloc(1, sizeof(IsoGfx)); | 265 | // Handle recreation by destroying the previous world. |
228 | if (!iso) { | 266 | destroy_world(iso); |
229 | return 0; | ||
230 | } | ||
231 | |||
232 | iso->screen_width = desc->screen_width; | ||
233 | iso->screen_height = desc->screen_height; | ||
234 | iso->tile_width = desc->tile_width; | ||
235 | iso->tile_height = desc->tile_height; | ||
236 | iso->world_width = desc->world_width; | ||
237 | iso->world_height = desc->world_height; | ||
238 | iso->max_num_tiles = | ||
239 | desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES; | ||
240 | 267 | ||
241 | const int world_size = desc->world_width * desc->world_height; | 268 | iso->tile_width = desc->tile_width; |
242 | const int screen_size = desc->screen_width * desc->screen_height; | 269 | iso->tile_height = desc->tile_height; |
243 | const int tile_size = desc->tile_width * desc->tile_height; | 270 | iso->world_width = desc->world_width; |
271 | iso->world_height = desc->world_height; | ||
244 | 272 | ||
273 | const int world_size = desc->world_width * desc->world_height; | ||
274 | const int tile_size = desc->tile_width * desc->tile_height; | ||
245 | const int tile_size_bytes = tile_size * (int)sizeof(Pixel); | 275 | const int tile_size_bytes = tile_size * (int)sizeof(Pixel); |
276 | const int tile_pool_size = | ||
277 | desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES; | ||
246 | 278 | ||
247 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { | 279 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { |
248 | goto cleanup; | 280 | goto cleanup; |
249 | } | 281 | } |
250 | if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { | 282 | if (!mempool_make_dyn(&iso->tiles, tile_pool_size, tile_size_bytes)) { |
283 | goto cleanup; | ||
284 | } | ||
285 | |||
286 | return true; | ||
287 | |||
288 | cleanup: | ||
289 | destroy_world(iso); | ||
290 | mempool_del(&iso->tiles); | ||
291 | return false; | ||
292 | } | ||
293 | |||
294 | bool isogfx_load_world(IsoGfx* iso, const char* filepath) { | ||
295 | assert(iso); | ||
296 | assert(filepath); | ||
297 | |||
298 | bool success = false; | ||
299 | |||
300 | // Handle recreation by destroying the previous world. | ||
301 | destroy_world(iso); | ||
302 | |||
303 | // Load the map. | ||
304 | printf("Load tile map: %s\n", filepath); | ||
305 | Tm_Map* map = read_file(filepath); | ||
306 | if (!map) { | ||
251 | goto cleanup; | 307 | goto cleanup; |
252 | } | 308 | } |
253 | if (!(iso->tile_mask = calloc(tile_size, sizeof(uint8_t)))) { | 309 | |
310 | // Allocate memory for the map and tile sets. | ||
311 | const int world_size = map->world_width * map->world_height; | ||
312 | const int base_tile_size = map->base_tile_width * map->base_tile_height; | ||
313 | const int base_tile_size_bytes = base_tile_size * (int)sizeof(Pixel); | ||
314 | // TODO: Need to get the total number of tiles from the map. | ||
315 | const int tile_pool_size = DEFAULT_MAX_NUM_TILES; | ||
316 | |||
317 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { | ||
254 | goto cleanup; | 318 | goto cleanup; |
255 | } | 319 | } |
256 | if (!mempool_make_dyn(&iso->tiles, iso->max_num_tiles, tile_size_bytes)) { | 320 | if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) { |
321 | goto cleanup; | ||
322 | } | ||
323 | if (!mempool_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) { | ||
257 | goto cleanup; | 324 | goto cleanup; |
258 | } | 325 | } |
259 | 326 | ||
260 | make_tile_mask(iso); | 327 | // Load the tile sets. |
328 | const Tm_Layer* layer = &map->layers[0]; | ||
329 | // TODO: Handle num_layers layers. | ||
330 | for (int i = 0; i < 1; ++i) { | ||
331 | const char* ts_path = layer->tileset_path; | ||
332 | |||
333 | // Tile set path is relative to the tile map file. Make it relative to the | ||
334 | // current working directory before loading. | ||
335 | char ts_path_cwd[PATH_MAX] = {0}; | ||
336 | if (!make_relative_path(MAX_PATH_LENGTH, filepath, ts_path, ts_path_cwd)) { | ||
337 | goto cleanup; | ||
338 | } | ||
261 | 339 | ||
262 | return iso; | 340 | Ts_TileSet* tileset = read_file(ts_path_cwd); |
341 | if (!tileset) { | ||
342 | goto cleanup; | ||
343 | }; | ||
344 | |||
345 | // Load tile data. | ||
346 | const Ts_Tile* tile = &tileset->tiles[0]; | ||
347 | for (uint16_t j = 0; j < tileset->num_tiles; ++j) { | ||
348 | // Tile dimensions should be a multiple of the base tile size. | ||
349 | assert((tile->width % map->base_tile_width) == 0); | ||
350 | assert((tile->height % map->base_tile_height) == 0); | ||
351 | |||
352 | const uint16_t tile_size = tile->width * tile->height; | ||
353 | |||
354 | // TODO: Add function in mempool to alloc N consecutive blocks. | ||
355 | const int num_blocks = tile_size / base_tile_size; | ||
356 | Pixel* pixels = mempool_alloc(&iso->pixels); | ||
357 | assert(pixels); | ||
358 | // This is ugly and assumes that blocks are allocated consecutively. | ||
359 | for (int b = 1; b < num_blocks; ++b) { | ||
360 | Pixel* block = mempool_alloc(&iso->pixels); | ||
361 | assert(block); | ||
362 | } | ||
363 | memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel)); | ||
263 | 364 | ||
264 | cleanup: | 365 | TileData* tile_data = mempool_alloc(&iso->tiles); |
265 | isogfx_del(&iso); | 366 | assert(tile_data); |
266 | return 0; | 367 | tile_data->width = tile->width; |
267 | } | 368 | tile_data->height = tile->height; |
369 | tile_data->num_blocks = (uint16_t)num_blocks; | ||
370 | tile_data->pixels_index = | ||
371 | (uint16_t)mempool_get_block_index(&iso->pixels, pixels); | ||
268 | 372 | ||
269 | void isogfx_del(IsoGfx** pIso) { | 373 | tile = ts_tileset_get_next_tile(tileset, tile); |
270 | assert(pIso); | ||
271 | IsoGfx* iso = *pIso; | ||
272 | if (iso) { | ||
273 | if (iso->world) { | ||
274 | free(iso->world); | ||
275 | } | 374 | } |
276 | if (iso->screen) { | 375 | |
277 | free(iso->screen); | 376 | printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd); |
377 | |||
378 | free(tileset); | ||
379 | layer = tm_map_get_next_layer(map, layer); | ||
380 | } | ||
381 | |||
382 | // Load the map into the world. | ||
383 | layer = &map->layers[0]; | ||
384 | // TODO: Handle num_layers layers. | ||
385 | for (int i = 0; i < 1; ++i) { | ||
386 | memcpy(iso->world, layer->tiles, world_size * sizeof(Tile)); | ||
387 | |||
388 | // TODO: We need to handle 'firsgid' in TMX files. | ||
389 | for (int j = 0; j < world_size; ++j) { | ||
390 | iso->world[j] -= 1; | ||
278 | } | 391 | } |
279 | if (iso->tile_mask) { | 392 | |
280 | free(iso->tile_mask); | 393 | layer = tm_map_get_next_layer(map, layer); |
394 | } | ||
395 | |||
396 | iso->world_width = map->world_width; | ||
397 | iso->world_height = map->world_height; | ||
398 | iso->tile_width = map->base_tile_width; | ||
399 | iso->tile_height = map->base_tile_height; | ||
400 | |||
401 | success = true; | ||
402 | |||
403 | cleanup: | ||
404 | if (map) { | ||
405 | free(map); | ||
406 | } | ||
407 | if (!success) { | ||
408 | destroy_world(iso); | ||
409 | } | ||
410 | return success; | ||
411 | } | ||
412 | |||
413 | int isogfx_world_width(const IsoGfx* iso) { | ||
414 | assert(iso); | ||
415 | return iso->world_width; | ||
416 | } | ||
417 | |||
418 | int isogfx_world_height(const IsoGfx* iso) { | ||
419 | assert(iso); | ||
420 | return iso->world_height; | ||
421 | } | ||
422 | |||
423 | /// Create a tile mask procedurally. | ||
424 | static void make_tile_from_colour( | ||
425 | const IsoGfx* iso, Pixel colour, TileData* tile) { | ||
426 | assert(iso); | ||
427 | assert(tile); | ||
428 | |||
429 | const int width = tile->width; | ||
430 | const int height = tile->height; | ||
431 | const int r = width / height; | ||
432 | |||
433 | for (int y = 0; y < height / 2; ++y) { | ||
434 | const int mask_start = width / 2 - r * y - 1; | ||
435 | const int mask_end = width / 2 + r * y + 1; | ||
436 | for (int x = 0; x < width; ++x) { | ||
437 | const bool mask = (mask_start <= x) && (x <= mask_end); | ||
438 | const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0}; | ||
439 | |||
440 | // Top half. | ||
441 | *tile_xy_mut(iso, tile, x, y) = val; | ||
442 | |||
443 | // Bottom half reflects the top half. | ||
444 | const int y_reflected = height - y - 1; | ||
445 | *tile_xy_mut(iso, tile, x, y_reflected) = val; | ||
281 | } | 446 | } |
282 | mempool_del(&iso->tiles); | ||
283 | free(iso); | ||
284 | } | 447 | } |
285 | } | 448 | } |
286 | 449 | ||
287 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { | 450 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { |
288 | assert(iso); | 451 | assert(iso); |
289 | assert(desc); | 452 | assert(desc); |
453 | // Client must create world before creating tiles. | ||
454 | assert(iso->tile_width > 0); | ||
455 | assert(iso->tile_height > 0); | ||
290 | 456 | ||
291 | TileData* tile = mempool_alloc(&iso->tiles); | 457 | TileData* tile = mempool_alloc(&iso->tiles); |
292 | assert(tile); // TODO: Make this a hard assert. | 458 | assert(tile); // TODO: Make this a hard assert. |
293 | 459 | ||
460 | tile->width = desc->width; | ||
461 | tile->height = desc->height; | ||
462 | |||
294 | switch (desc->type) { | 463 | switch (desc->type) { |
295 | case TileFromColour: | 464 | case TileFromColour: |
296 | make_tile_from_colour(iso, desc->colour, tile); | 465 | make_tile_from_colour(iso, desc->colour, tile); |
@@ -311,6 +480,88 @@ void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { | |||
311 | *world_xy_mut(iso, x, y) = tile; | 480 | *world_xy_mut(iso, x, y) = tile; |
312 | } | 481 | } |
313 | 482 | ||
483 | void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) { | ||
484 | assert(iso); | ||
485 | for (int y = y0; y < y1; ++y) { | ||
486 | for (int x = x0; x < x1; ++x) { | ||
487 | isogfx_set_tile(iso, x, y, tile); | ||
488 | } | ||
489 | } | ||
490 | } | ||
491 | |||
492 | // ----------------------------------------------------------------------------- | ||
493 | // Rendering and picking. | ||
494 | // ----------------------------------------------------------------------------- | ||
495 | |||
496 | static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { | ||
497 | assert(iso); | ||
498 | |||
499 | const TileData* tile_data = mempool_get_block(&iso->tiles, tile); | ||
500 | assert(tile_data); | ||
501 | |||
502 | // Tile can exceed screen bounds, so we must clip it. | ||
503 | #define max(a, b) (a > b ? a : b) | ||
504 | const int py_offset = max(0, (int)tile_data->height - origin.y); | ||
505 | origin.y = max(0, origin.y - (int)tile_data->height); | ||
506 | |||
507 | // Clip along Y and X as we draw. | ||
508 | for (int py = py_offset; | ||
509 | (py < tile_data->height) && (origin.y + py < iso->screen_height); ++py) { | ||
510 | const int sy = origin.y + py - py_offset; | ||
511 | for (int px = 0; | ||
512 | (px < tile_data->width) && (origin.x + px < iso->screen_width); ++px) { | ||
513 | const Pixel colour = tile_xy(iso, tile_data, px, py); | ||
514 | if (colour.a > 0) { | ||
515 | const int sx = origin.x + px; | ||
516 | *screen_xy_mut(iso, sx, sy) = colour; | ||
517 | } | ||
518 | } | ||
519 | } | ||
520 | |||
521 | // for (int py = 0; py < tile_data->height; ++py) { | ||
522 | // for (int px = 0; px < tile_data->width; ++px) { | ||
523 | // const Pixel colour = tile_xy(iso, tile_data, px, py); | ||
524 | // if (colour.a > 0) { | ||
525 | // const int sx = origin.x + px; | ||
526 | // const int sy = origin.y + py; | ||
527 | // if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) && | ||
528 | // (sy < iso->screen_height)) { | ||
529 | // *screen_xy_mut(iso, sx, sy) = colour; | ||
530 | // } | ||
531 | // } | ||
532 | // } | ||
533 | // } | ||
534 | } | ||
535 | |||
536 | static void draw(IsoGfx* iso) { | ||
537 | assert(iso); | ||
538 | |||
539 | const int W = iso->screen_width; | ||
540 | const int H = iso->screen_height; | ||
541 | |||
542 | memset(iso->screen, 0, W * H * sizeof(Pixel)); | ||
543 | |||
544 | // const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; | ||
545 | const ivec2 o = { | ||
546 | (iso->screen_width / 2) - (iso->tile_width / 2), iso->tile_height}; | ||
547 | const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; | ||
548 | const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; | ||
549 | |||
550 | // TODO: Culling. | ||
551 | // Ex: map the screen corners to tile space to cull. | ||
552 | // Ex: walk in screen space and fetch the tile. | ||
553 | // The tile-centric approach might be more cache-friendly since the | ||
554 | // screen-centric approach would juggle multiple tiles throughout the scan. | ||
555 | for (int ty = 0; ty < iso->world_height; ++ty) { | ||
556 | for (int tx = 0; tx < iso->world_width; ++tx) { | ||
557 | const Tile tile = world_xy(iso, tx, ty); | ||
558 | const ivec2 so = | ||
559 | ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty))); | ||
560 | draw_tile(iso, so, tile); | ||
561 | } | ||
562 | } | ||
563 | } | ||
564 | |||
314 | void isogfx_pick_tile( | 565 | void isogfx_pick_tile( |
315 | const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { | 566 | const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { |
316 | assert(iso); | 567 | assert(iso); |
@@ -356,13 +607,3 @@ const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) { | |||
356 | assert(iso); | 607 | assert(iso); |
357 | return iso->screen; | 608 | return iso->screen; |
358 | } | 609 | } |
359 | |||
360 | int isogfx_world_width(const IsoGfx* iso) { | ||
361 | assert(iso); | ||
362 | return iso->world_width; | ||
363 | } | ||
364 | |||
365 | int isogfx_world_height(const IsoGfx* iso) { | ||
366 | assert(iso); | ||
367 | return iso->world_height; | ||
368 | } | ||