summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gfx-iso/CMakeLists.txt11
-rw-r--r--gfx-iso/app/app.h11
-rw-r--r--gfx-iso/app/checkerboard.c120
-rw-r--r--gfx-iso/app/checkerboard.h9
-rw-r--r--gfx-iso/app/isogfx-demo.c67
-rw-r--r--gfx-iso/app/isogfx-demo.h9
-rw-r--r--gfx-iso/app/main.c (renamed from gfx-iso/demo/isogfx-demo.c)80
-rw-r--r--gfx-iso/asset/mkasset.py155
-rw-r--r--gfx-iso/include/isogfx/isogfx.h63
-rw-r--r--gfx-iso/src/isogfx.c561
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
15target_link_libraries(isogfx PRIVATE 15target_link_libraries(isogfx PRIVATE
16 filesystem
16 mempool) 17 mempool)
17 18
18target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) 19target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic)
19 20
20# Demo 21# Demo
21 22
22project(isogfx-demo) 23project(isogfx-app)
23 24
24add_executable(isogfx-demo 25add_executable(isogfx-app
25 demo/isogfx-demo.c) 26 app/checkerboard.c
27 app/isogfx-demo.c
28 app/main.c)
26 29
27target_link_libraries(isogfx-demo PRIVATE 30target_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
3typedef struct IsoGfx IsoGfx;
4typedef struct IsoGfxApp IsoGfxApp;
5
6typedef 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
11static const int TILE_WIDTH = 64;
12static const int TILE_HEIGHT = TILE_WIDTH / 2;
13static const int WORLD_WIDTH = 20;
14static const int WORLD_HEIGHT = 20;
15
16static 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
31typedef enum Colour {
32 Black,
33 White,
34 Red,
35} Colour;
36
37typedef struct State {
38 Tile red;
39 int xpick;
40 int ypick;
41} State;
42
43static 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
55static void shutdown(IsoGfx* iso, void* app_state) {
56 assert(iso);
57 if (app_state) {
58 free(app_state);
59 }
60}
61
62static 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
75static 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
86bool 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
117cleanup:
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
7typedef struct IsoGfxApp IsoGfxApp;
8
9bool 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
11typedef struct State {
12 int xpick;
13 int ypick;
14} State;
15
16static void shutdown(IsoGfx* iso, void* app_state) {
17 assert(iso);
18 if (app_state) {
19 free(app_state);
20 }
21}
22
23static 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
36static 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
44bool 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
64cleanup:
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
7typedef struct IsoGfxApp IsoGfxApp;
8
9bool 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
16static const int SCREEN_WIDTH = 1408; 19static const int SCREEN_WIDTH = 1408;
17static const int SCREEN_HEIGHT = 960; 20static const int SCREEN_HEIGHT = 960;
18static const int TILE_WIDTH = 64;
19static const int TILE_HEIGHT = TILE_WIDTH / 2;
20static const int WORLD_WIDTH = 20;
21static const int WORLD_HEIGHT = 20;
22
23static const Pixel BLACK = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46};
24static const Pixel WHITE = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0};
25static const Pixel RED = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84};
26 21
27typedef struct State { 22typedef 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
37static 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
49static bool init(const GfxAppDesc* desc, void** app_state) { 30static 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:
139static void shutdown(void* app_state) { 112static 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
159static void render(void* app_state) { 133static 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).
6import argparse
7import ctypes
8from PIL import Image
9import sys
10from xml.etree import ElementTree
11
12# Maximum length of path strings in .TS and .TM files.
13MAX_PATH_LENGTH = 128
14
15
16def drop_extension(filepath):
17 return filepath[:filepath.rfind('.')]
18
19
20def 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
32def 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
74def 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
136def 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
154if __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
8typedef struct IsoGfx IsoGfx; 9typedef struct IsoGfx IsoGfx;
9 10
10typedef uint8_t Tile; 11/// Tile handle.
12typedef uint16_t Tile;
13
14/// Colour channel.
11typedef uint8_t Channel; 15typedef uint8_t Channel;
12 16
13typedef struct Pixel { 17typedef struct Pixel {
14 Channel r, g, b; 18 Channel r, g, b, a;
15} Pixel; 19} Pixel;
16 20
17typedef enum TileDescType { 21typedef enum TileDescType {
18 TileFromColour, 22 TileFromColour,
19 TileFromFile, 23 TileFromFile,
20 TileFromMemory 24 TileFromMemory,
21} TileDescType; 25} TileDescType;
22 26
23typedef struct TileDesc { 27typedef 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
42typedef 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
36typedef struct IsoGfxDesc { 50typedef 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.
46IsoGfx* isogfx_new(const IsoGfxDesc*); 56IsoGfx* isogfx_new(const IsoGfxDesc*);
47 57
58/// Destroy the isometric graphics engine.
48void isogfx_del(IsoGfx**); 59void isogfx_del(IsoGfx**);
49 60
61/// Create an empty world.
62bool isogfx_make_world(IsoGfx*, const WorldDesc*);
63
64/// Load a world from a tile map (.TM) file.
65bool isogfx_load_world(IsoGfx*, const char* filepath);
66
67/// Return the world's width.
68int isogfx_world_width(const IsoGfx*);
69
70/// Return the world's height.
71int isogfx_world_height(const IsoGfx*);
72
73/// Create a new tile.
50Tile isogfx_make_tile(IsoGfx*, const TileDesc*); 74Tile isogfx_make_tile(IsoGfx*, const TileDesc*);
51 75
76/// Set the tile at position (x,y).
52void isogfx_set_tile(IsoGfx*, int x, int y, Tile); 77void isogfx_set_tile(IsoGfx*, int x, int y, Tile);
53 78
79/// Set the tiles in positions in the range (x0,y0) - (x1,y1).
54void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); 80void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile);
55 81
82/// Translate Cartesian to isometric coordinates.
56void isogfx_pick_tile( 83void 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.
59void isogfx_render(IsoGfx*); 87void 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().
61void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); 94void 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.
63const Pixel* isogfx_get_screen_buffer(const IsoGfx*); 99const Pixel* isogfx_get_screen_buffer(const IsoGfx*);
64
65int isogfx_world_width(const IsoGfx*);
66int 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
25typedef 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
31typedef 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
38typedef 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
45typedef 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
54static 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
63static 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
14typedef struct TileData { 77typedef 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
18DEF_MEMPOOL_DYN(TilePool, TileData) 84DEF_MEMPOOL_DYN(TilePool, TileData)
85DEF_MEMPOOL_DYN(PixelPool, Pixel)
19 86
20typedef struct IsoGfx { 87typedef 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
34typedef struct ivec2 { 104typedef 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
73Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { 143static 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
84Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { 155static 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
95static inline Tile world_xy(IsoGfx* iso, int x, int y) { 159static 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
104static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { 163static 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
113static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { 172static 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
122static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { 176static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
177 return (Tile*)world_xy_const_ref(iso, x, y);
178}
179
180static 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
131static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) { 190static 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
153static void draw(IsoGfx* iso) { 194static 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}; 202IsoGfx* 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.
181static 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
225cleanup:
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.
203static void make_tile_from_colour( 231static 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) { 241void 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
215IsoGfx* isogfx_new(const IsoGfxDesc* desc) { 255bool 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
288cleanup:
289 destroy_world(iso);
290 mempool_del(&iso->tiles);
291 return false;
292}
293
294bool 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
264cleanup: 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
269void 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
403cleanup:
404 if (map) {
405 free(map);
406 }
407 if (!success) {
408 destroy_world(iso);
409 }
410 return success;
411}
412
413int isogfx_world_width(const IsoGfx* iso) {
414 assert(iso);
415 return iso->world_width;
416}
417
418int isogfx_world_height(const IsoGfx* iso) {
419 assert(iso);
420 return iso->world_height;
421}
422
423/// Create a tile mask procedurally.
424static 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
287Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { 450Tile 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
483void 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
496static 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
536static 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
314void isogfx_pick_tile( 565void 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
360int isogfx_world_width(const IsoGfx* iso) {
361 assert(iso);
362 return iso->world_width;
363}
364
365int isogfx_world_height(const IsoGfx* iso) {
366 assert(iso);
367 return iso->world_height;
368}