diff options
author | 3gg <3gg@shellblade.net> | 2023-06-24 18:46:33 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2023-06-24 18:46:33 -0700 |
commit | 0831d5bce79008bfa6404f8e8116ae8290442fde (patch) | |
tree | e488c719e16b34b60126837a90a44d1c3dd552ee | |
parent | cf886f4fa406ddd48f30c00ad3c77f9dc134af3a (diff) |
Isometric Renderer initial commit.
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | gfx-iso/CMakeLists.txt | 30 | ||||
-rw-r--r-- | gfx-iso/demo/isogfx-demo.c | 213 | ||||
-rw-r--r-- | gfx-iso/include/isogfx/isogfx.h | 66 | ||||
-rw-r--r-- | gfx-iso/src/isogfx.c | 361 | ||||
-rw-r--r-- | gfx/CMakeLists.txt | 4 |
6 files changed, 673 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2aac1ab..3a0cd5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -2,4 +2,5 @@ cmake_minimum_required(VERSION 3.0) | |||
2 | 2 | ||
3 | add_subdirectory(gfx) | 3 | add_subdirectory(gfx) |
4 | add_subdirectory(gfx-app) | 4 | add_subdirectory(gfx-app) |
5 | add_subdirectory(gfx-iso) | ||
5 | add_subdirectory(gltfview) | 6 | add_subdirectory(gltfview) |
diff --git a/gfx-iso/CMakeLists.txt b/gfx-iso/CMakeLists.txt new file mode 100644 index 0000000..8f95f7f --- /dev/null +++ b/gfx-iso/CMakeLists.txt | |||
@@ -0,0 +1,30 @@ | |||
1 | cmake_minimum_required(VERSION 3.0) | ||
2 | |||
3 | project(isogfx) | ||
4 | |||
5 | set(CMAKE_C_STANDARD 11) | ||
6 | set(CMAKE_C_STANDARD_REQUIRED On) | ||
7 | set(CMAKE_C_EXTENSIONS Off) | ||
8 | |||
9 | add_library(isogfx | ||
10 | src/isogfx.c) | ||
11 | |||
12 | target_include_directories(isogfx PUBLIC | ||
13 | include) | ||
14 | |||
15 | target_link_libraries(isogfx PRIVATE | ||
16 | mempool) | ||
17 | |||
18 | target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic) | ||
19 | |||
20 | # Demo | ||
21 | |||
22 | project(isogfx-demo) | ||
23 | |||
24 | add_executable(isogfx-demo | ||
25 | demo/isogfx-demo.c) | ||
26 | |||
27 | target_link_libraries(isogfx-demo PRIVATE | ||
28 | gfx | ||
29 | gfx-app | ||
30 | isogfx) | ||
diff --git a/gfx-iso/demo/isogfx-demo.c b/gfx-iso/demo/isogfx-demo.c new file mode 100644 index 0000000..d6c1ab0 --- /dev/null +++ b/gfx-iso/demo/isogfx-demo.c | |||
@@ -0,0 +1,213 @@ | |||
1 | #include <isogfx/isogfx.h> | ||
2 | |||
3 | #include <gfx/gfx.h> | ||
4 | #include <gfx/gfx_app.h> | ||
5 | #include <gfx/render_backend.h> | ||
6 | #include <gfx/renderer.h> | ||
7 | #include <gfx/scene.h> | ||
8 | #include <gfx/util/geometry.h> | ||
9 | #include <gfx/util/shader.h> | ||
10 | |||
11 | #include <assert.h> | ||
12 | #include <stdbool.h> | ||
13 | #include <stdio.h> | ||
14 | #include <stdlib.h> | ||
15 | |||
16 | static const int SCREEN_WIDTH = 1408; | ||
17 | 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 | |||
27 | typedef struct State { | ||
28 | Gfx* gfx; | ||
29 | IsoGfx* iso; | ||
30 | Tile red; | ||
31 | int xpick; | ||
32 | int ypick; | ||
33 | Texture* screen_texture; | ||
34 | Scene* scene; | ||
35 | } State; | ||
36 | |||
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) { | ||
50 | State* state = calloc(1, sizeof(State)); | ||
51 | if (!state) { | ||
52 | return false; | ||
53 | } | ||
54 | |||
55 | if (!(state->iso = isogfx_new(&(IsoGfxDesc){ | ||
56 | .screen_width = SCREEN_WIDTH, | ||
57 | .screen_height = SCREEN_HEIGHT, | ||
58 | .tile_width = TILE_WIDTH, | ||
59 | .tile_height = TILE_HEIGHT, | ||
60 | .world_width = WORLD_WIDTH, | ||
61 | .world_height = WORLD_HEIGHT}))) { | ||
62 | goto cleanup; | ||
63 | } | ||
64 | if (!(state->gfx = gfx_init())) { | ||
65 | goto cleanup; | ||
66 | } | ||
67 | RenderBackend* render_backend = gfx_get_render_backend(state->gfx); | ||
68 | |||
69 | if (!(state->screen_texture = gfx_make_texture( | ||
70 | render_backend, &(TextureDesc){ | ||
71 | .width = SCREEN_WIDTH, | ||
72 | .height = SCREEN_HEIGHT, | ||
73 | .dimension = Texture2D, | ||
74 | .format = TextureRGB8, | ||
75 | .filtering = NearestFiltering, | ||
76 | .wrap = ClampToEdge, | ||
77 | .mipmaps = false}))) { | ||
78 | goto cleanup; | ||
79 | } | ||
80 | |||
81 | ShaderProgram* shader = gfx_make_view_texture_shader(render_backend); | ||
82 | if (!shader) { | ||
83 | goto cleanup; | ||
84 | } | ||
85 | |||
86 | Geometry* geometry = gfx_make_quad_11(render_backend); | ||
87 | if (!geometry) { | ||
88 | goto cleanup; | ||
89 | } | ||
90 | |||
91 | MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1}; | ||
92 | material_desc.uniforms[0] = (ShaderUniform){ | ||
93 | .type = UniformTexture, | ||
94 | .value.texture = state->screen_texture, | ||
95 | .name = sstring_make("Texture")}; | ||
96 | Material* material = gfx_make_material(&material_desc); | ||
97 | if (!material) { | ||
98 | return false; | ||
99 | } | ||
100 | |||
101 | const MeshDesc mesh_desc = | ||
102 | (MeshDesc){.geometry = geometry, .material = material, .shader = shader}; | ||
103 | Mesh* mesh = gfx_make_mesh(&mesh_desc); | ||
104 | if (!mesh) { | ||
105 | goto cleanup; | ||
106 | } | ||
107 | |||
108 | SceneObject* object = gfx_make_object(); | ||
109 | if (!object) { | ||
110 | goto cleanup; | ||
111 | } | ||
112 | gfx_add_object_mesh(object, mesh); | ||
113 | |||
114 | state->scene = gfx_make_scene(); | ||
115 | SceneNode* node = gfx_make_object_node(object); | ||
116 | SceneNode* root = gfx_get_scene_root(state->scene); | ||
117 | gfx_set_node_parent(node, root); | ||
118 | |||
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; | ||
129 | return true; | ||
130 | |||
131 | cleanup: | ||
132 | if (state->gfx) { | ||
133 | gfx_destroy(&state->gfx); | ||
134 | } | ||
135 | free(state); | ||
136 | return false; | ||
137 | } | ||
138 | |||
139 | static void shutdown(void* app_state) { | ||
140 | assert(app_state); | ||
141 | State* state = (State*)(app_state); | ||
142 | isogfx_del(&state->iso); | ||
143 | gfx_destroy(&state->gfx); | ||
144 | free(app_state); | ||
145 | } | ||
146 | |||
147 | static void update(void* app_state, double t, double dt) { | ||
148 | assert(app_state); | ||
149 | State* state = (State*)(app_state); | ||
150 | |||
151 | double mouse_x, mouse_y; | ||
152 | gfx_app_get_mouse_position(&mouse_x, &mouse_y); | ||
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 | } | ||
158 | |||
159 | static void render(void* app_state) { | ||
160 | assert(app_state); | ||
161 | State* state = (State*)(app_state); | ||
162 | |||
163 | isogfx_render(state->iso); | ||
164 | if ((state->xpick != -1) && (state->ypick != -1)) { | ||
165 | isogfx_draw_tile(state->iso, state->xpick, state->ypick, state->red); | ||
166 | } | ||
167 | |||
168 | const Pixel* screen = isogfx_get_screen_buffer(state->iso); | ||
169 | assert(screen); | ||
170 | gfx_update_texture( | ||
171 | state->screen_texture, &(TextureDataDesc){.pixels = screen}); | ||
172 | |||
173 | RenderBackend* render_backend = gfx_get_render_backend(state->gfx); | ||
174 | Renderer* renderer = gfx_get_renderer(state->gfx); | ||
175 | |||
176 | gfx_start_frame(render_backend); | ||
177 | gfx_render_scene( | ||
178 | renderer, &(RenderSceneParams){ | ||
179 | .mode = RenderDefault, .scene = state->scene, .camera = 0}); | ||
180 | gfx_end_frame(render_backend); | ||
181 | } | ||
182 | |||
183 | static void resize(void* app_state, int width, int height) { | ||
184 | assert(app_state); | ||
185 | State* state = (State*)(app_state); | ||
186 | |||
187 | RenderBackend* render_backend = gfx_get_render_backend(state->gfx); | ||
188 | gfx_set_viewport(render_backend, width, height); | ||
189 | } | ||
190 | |||
191 | int main(int argc, const char** argv) { | ||
192 | const int initial_width = SCREEN_WIDTH; | ||
193 | const int initial_height = SCREEN_HEIGHT; | ||
194 | const int max_fps = 60; | ||
195 | |||
196 | gfx_app_run( | ||
197 | &(GfxAppDesc){ | ||
198 | .argc = argc, | ||
199 | .argv = argv, | ||
200 | .width = initial_width, | ||
201 | .height = initial_height, | ||
202 | .max_fps = max_fps, | ||
203 | .update_delta_time = max_fps > 0 ? 1.0 / (double)max_fps : 0.0, | ||
204 | .title = "Isometric Renderer"}, | ||
205 | &(GfxAppCallbacks){ | ||
206 | .init = init, | ||
207 | .update = update, | ||
208 | .render = render, | ||
209 | .resize = resize, | ||
210 | .shutdown = shutdown}); | ||
211 | |||
212 | return 0; | ||
213 | } | ||
diff --git a/gfx-iso/include/isogfx/isogfx.h b/gfx-iso/include/isogfx/isogfx.h new file mode 100644 index 0000000..a5f7770 --- /dev/null +++ b/gfx-iso/include/isogfx/isogfx.h | |||
@@ -0,0 +1,66 @@ | |||
1 | /* | ||
2 | * Isometric rendering engine. | ||
3 | */ | ||
4 | #pragma once | ||
5 | |||
6 | #include <stdint.h> | ||
7 | |||
8 | typedef struct IsoGfx IsoGfx; | ||
9 | |||
10 | typedef uint8_t Tile; | ||
11 | typedef uint8_t Channel; | ||
12 | |||
13 | typedef struct Pixel { | ||
14 | Channel r, g, b; | ||
15 | } Pixel; | ||
16 | |||
17 | typedef enum TileDescType { | ||
18 | TileFromColour, | ||
19 | TileFromFile, | ||
20 | TileFromMemory | ||
21 | } TileDescType; | ||
22 | |||
23 | typedef struct TileDesc { | ||
24 | TileDescType type; | ||
25 | union { | ||
26 | Pixel colour; | ||
27 | struct { | ||
28 | const char* path; | ||
29 | } file; | ||
30 | struct { | ||
31 | const void* data; | ||
32 | } mem; | ||
33 | }; | ||
34 | } TileDesc; | ||
35 | |||
36 | typedef struct IsoGfxDesc { | ||
37 | int screen_width; | ||
38 | int screen_height; | ||
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; | ||
45 | |||
46 | IsoGfx* isogfx_new(const IsoGfxDesc*); | ||
47 | |||
48 | void isogfx_del(IsoGfx**); | ||
49 | |||
50 | Tile isogfx_make_tile(IsoGfx*, const TileDesc*); | ||
51 | |||
52 | void isogfx_set_tile(IsoGfx*, int x, int y, Tile); | ||
53 | |||
54 | void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile); | ||
55 | |||
56 | void isogfx_pick_tile( | ||
57 | const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso); | ||
58 | |||
59 | void isogfx_render(IsoGfx*); | ||
60 | |||
61 | void isogfx_draw_tile(IsoGfx*, int x, int y, Tile); | ||
62 | |||
63 | 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 new file mode 100644 index 0000000..27981f9 --- /dev/null +++ b/gfx-iso/src/isogfx.c | |||
@@ -0,0 +1,361 @@ | |||
1 | #include <isogfx/isogfx.h> | ||
2 | |||
3 | #include <mempool.h> | ||
4 | |||
5 | #include <assert.h> | ||
6 | #include <stdbool.h> | ||
7 | #include <stdint.h> | ||
8 | #include <stdlib.h> | ||
9 | #include <string.h> | ||
10 | |||
11 | /// Maximum number of tiles unless the user chooses a non-zero value. | ||
12 | #define DEFAULT_MAX_NUM_TILES 1024 | ||
13 | |||
14 | typedef struct TileData { | ||
15 | Pixel pixels[1]; // Dynamically allocated. | ||
16 | } TileData; | ||
17 | |||
18 | DEF_MEMPOOL_DYN(TilePool, TileData) | ||
19 | |||
20 | typedef struct IsoGfx { | ||
21 | Tile* world; | ||
22 | Pixel* screen; | ||
23 | uint8_t* tile_mask; | ||
24 | TilePool tiles; | ||
25 | int screen_width; | ||
26 | int screen_height; | ||
27 | int tile_width; | ||
28 | int tile_height; | ||
29 | int world_width; | ||
30 | int world_height; | ||
31 | int max_num_tiles; | ||
32 | } IsoGfx; | ||
33 | |||
34 | typedef struct ivec2 { | ||
35 | int x, y; | ||
36 | } ivec2; | ||
37 | |||
38 | typedef struct vec2 { | ||
39 | double x, y; | ||
40 | } vec2; | ||
41 | |||
42 | static inline ivec2 ivec2_add(ivec2 a, ivec2 b) { | ||
43 | return (ivec2){.x = a.x + b.x, .y = a.y + b.y}; | ||
44 | } | ||
45 | |||
46 | static inline ivec2 ivec2_scale(ivec2 a, int s) { | ||
47 | return (ivec2){.x = a.x * s, .y = a.y * s}; | ||
48 | } | ||
49 | |||
50 | static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) { | ||
51 | return (ivec2){ | ||
52 | .x = (iso.x - iso.y) * (s / 2) + (w / 2), .y = (iso.x + iso.y) * (t / 2)}; | ||
53 | } | ||
54 | |||
55 | static inline vec2 cart2iso(vec2 cart, int s, int t, int w) { | ||
56 | const double one_over_s = 1. / (double)s; | ||
57 | const double one_over_t = 1. / (double)t; | ||
58 | const double x = cart.x - (double)(w / 2); | ||
59 | |||
60 | return (vec2){ | ||
61 | .x = (int)(one_over_s * x + one_over_t * cart.y), | ||
62 | .y = (int)(-one_over_s * x + one_over_t * cart.y)}; | ||
63 | } | ||
64 | |||
65 | Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) { | ||
66 | assert(iso); | ||
67 | assert(tile); | ||
68 | assert(tile->pixels); | ||
69 | assert(x >= 0); | ||
70 | assert(y >= 0); | ||
71 | assert(x < iso->tile_width); | ||
72 | assert(y < iso->tile_height); | ||
73 | return &tile->pixels[y * iso->tile_width + x]; | ||
74 | } | ||
75 | |||
76 | Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) { | ||
77 | assert(iso); | ||
78 | assert(tile); | ||
79 | assert(tile->pixels); | ||
80 | assert(x >= 0); | ||
81 | assert(y >= 0); | ||
82 | assert(x < iso->tile_width); | ||
83 | assert(y < iso->tile_height); | ||
84 | return tile->pixels[y * iso->tile_width + x]; | ||
85 | } | ||
86 | |||
87 | static inline Tile world_xy(IsoGfx* iso, int x, int y) { | ||
88 | assert(iso); | ||
89 | assert(x >= 0); | ||
90 | assert(y >= 0); | ||
91 | assert(x < iso->world_width); | ||
92 | assert(y < iso->world_height); | ||
93 | return iso->world[y * iso->world_width + x]; | ||
94 | } | ||
95 | |||
96 | static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) { | ||
97 | assert(iso); | ||
98 | assert(x >= 0); | ||
99 | assert(y >= 0); | ||
100 | assert(x < iso->world_width); | ||
101 | assert(y < iso->world_height); | ||
102 | return &iso->world[y * iso->world_width + x]; | ||
103 | } | ||
104 | |||
105 | static inline Pixel screen_xy(IsoGfx* iso, int x, int y) { | ||
106 | assert(iso); | ||
107 | assert(x >= 0); | ||
108 | assert(y >= 0); | ||
109 | assert(x < iso->screen_width); | ||
110 | assert(y < iso->screen_height); | ||
111 | return iso->screen[y * iso->screen_width + x]; | ||
112 | } | ||
113 | |||
114 | static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) { | ||
115 | assert(iso); | ||
116 | assert(x >= 0); | ||
117 | assert(y >= 0); | ||
118 | assert(x < iso->screen_width); | ||
119 | assert(y < iso->screen_height); | ||
120 | return &iso->screen[y * iso->screen_width + x]; | ||
121 | } | ||
122 | |||
123 | static void draw_tile(IsoGfx* iso, ivec2 so, Tile tile) { | ||
124 | assert(iso); | ||
125 | |||
126 | const TileData* data = mempool_get_block(&iso->tiles, tile); | ||
127 | assert(data); | ||
128 | |||
129 | for (int py = 0; py < iso->tile_height; ++py) { | ||
130 | for (int px = 0; px < iso->tile_width; ++px) { | ||
131 | const Pixel colour = tile_xy(iso, data, px, py); | ||
132 | const int sx = so.x + px; | ||
133 | const int sy = so.y + py; | ||
134 | if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) && | ||
135 | (sy < iso->screen_height)) { | ||
136 | const uint8_t mask = iso->tile_mask[py * iso->tile_width + px]; | ||
137 | if (mask == 1) { | ||
138 | *screen_xy_mut(iso, sx, sy) = colour; | ||
139 | } | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | } | ||
144 | |||
145 | static void draw(IsoGfx* iso) { | ||
146 | assert(iso); | ||
147 | |||
148 | const int W = iso->screen_width; | ||
149 | const int H = iso->screen_height; | ||
150 | |||
151 | memset(iso->screen, 0, W * H * sizeof(Pixel)); | ||
152 | |||
153 | const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; | ||
154 | const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; | ||
155 | const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; | ||
156 | |||
157 | // TODO: Since the world will generally be larger than the screen, it | ||
158 | // would be best to walk in screen space and fetch the tile. | ||
159 | // The tile-centric approach might be more cache-friendly, however, since the | ||
160 | // screen-centric approach would juggle multiple tiles throughout the scan. | ||
161 | for (int ty = 0; ty < iso->world_height; ++ty) { | ||
162 | for (int tx = 0; tx < iso->world_width; ++tx) { | ||
163 | const Tile tile = world_xy(iso, tx, ty); | ||
164 | const ivec2 so = | ||
165 | ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty))); | ||
166 | draw_tile(iso, so, tile); | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | |||
171 | /// Creates a tile mask procedurally. | ||
172 | static void make_tile_mask(IsoGfx* iso) { | ||
173 | assert(iso); | ||
174 | assert(iso->tile_mask); | ||
175 | |||
176 | for (int y = 0; y < iso->tile_height / 2; ++y) { | ||
177 | const int mask_start = iso->tile_width / 2 - 2 * y - 1; | ||
178 | const int mask_end = iso->tile_width / 2 + 2 * y + 1; | ||
179 | for (int x = 0; x < iso->tile_width; ++x) { | ||
180 | const bool masked = (mask_start <= x) && (x <= mask_end); | ||
181 | const uint8_t val = masked ? 1 : 0; | ||
182 | |||
183 | // Top half. | ||
184 | iso->tile_mask[y * iso->tile_width + x] = val; | ||
185 | |||
186 | // Bottom half reflects the top half. | ||
187 | const int y_reflected = iso->tile_height - y - 1; | ||
188 | iso->tile_mask[y_reflected * iso->tile_width + x] = val; | ||
189 | } | ||
190 | } | ||
191 | } | ||
192 | |||
193 | /// Creates a tile with a constant colour. | ||
194 | static void make_tile_from_colour( | ||
195 | const IsoGfx* iso, Pixel colour, TileData* tile) { | ||
196 | assert(iso); | ||
197 | assert(tile); | ||
198 | |||
199 | for (int y = 0; y < iso->tile_height; ++y) { | ||
200 | for (int x = 0; x < iso->tile_width; ++x) { | ||
201 | *tile_xy_mut(iso, tile, x, y) = colour; | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | |||
206 | IsoGfx* isogfx_new(const IsoGfxDesc* desc) { | ||
207 | assert(desc->screen_width > 0); | ||
208 | assert(desc->screen_height > 0); | ||
209 | assert(desc->tile_width > 0); | ||
210 | assert(desc->tile_height > 0); | ||
211 | // Part of our implementation assumes even widths and heights for greater | ||
212 | // precision. | ||
213 | assert((desc->screen_width & 1) == 0); | ||
214 | assert((desc->screen_height & 1) == 0); | ||
215 | assert((desc->tile_width & 1) == 0); | ||
216 | assert((desc->tile_height & 1) == 0); | ||
217 | |||
218 | IsoGfx* iso = calloc(1, sizeof(IsoGfx)); | ||
219 | if (!iso) { | ||
220 | return 0; | ||
221 | } | ||
222 | |||
223 | iso->screen_width = desc->screen_width; | ||
224 | iso->screen_height = desc->screen_height; | ||
225 | iso->tile_width = desc->tile_width; | ||
226 | iso->tile_height = desc->tile_height; | ||
227 | iso->world_width = desc->world_width; | ||
228 | iso->world_height = desc->world_height; | ||
229 | iso->max_num_tiles = | ||
230 | desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES; | ||
231 | |||
232 | const int world_size = desc->world_width * desc->world_height; | ||
233 | const int screen_size = desc->screen_width * desc->screen_height; | ||
234 | const int tile_size = desc->tile_width * desc->tile_height; | ||
235 | |||
236 | const int tile_size_bytes = tile_size * (int)sizeof(Pixel); | ||
237 | |||
238 | if (!(iso->world = calloc(world_size, sizeof(Tile)))) { | ||
239 | goto cleanup; | ||
240 | } | ||
241 | if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) { | ||
242 | goto cleanup; | ||
243 | } | ||
244 | if (!(iso->tile_mask = calloc(tile_size, sizeof(uint8_t)))) { | ||
245 | goto cleanup; | ||
246 | } | ||
247 | if (!mempool_make_dyn(&iso->tiles, iso->max_num_tiles, tile_size_bytes)) { | ||
248 | goto cleanup; | ||
249 | } | ||
250 | |||
251 | make_tile_mask(iso); | ||
252 | |||
253 | return iso; | ||
254 | |||
255 | cleanup: | ||
256 | isogfx_del(&iso); | ||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | void isogfx_del(IsoGfx** pIso) { | ||
261 | assert(pIso); | ||
262 | IsoGfx* iso = *pIso; | ||
263 | if (iso) { | ||
264 | if (iso->world) { | ||
265 | free(iso->world); | ||
266 | } | ||
267 | if (iso->screen) { | ||
268 | free(iso->screen); | ||
269 | } | ||
270 | if (iso->tile_mask) { | ||
271 | free(iso->tile_mask); | ||
272 | } | ||
273 | mempool_del(&iso->tiles); | ||
274 | free(iso); | ||
275 | } | ||
276 | } | ||
277 | |||
278 | Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) { | ||
279 | assert(iso); | ||
280 | assert(desc); | ||
281 | |||
282 | TileData* tile = mempool_alloc(&iso->tiles); | ||
283 | assert(tile); // TODO: Make this a hard assert. | ||
284 | |||
285 | switch (desc->type) { | ||
286 | case TileFromColour: | ||
287 | make_tile_from_colour(iso, desc->colour, tile); | ||
288 | break; | ||
289 | case TileFromFile: | ||
290 | assert(false); // TODO | ||
291 | break; | ||
292 | case TileFromMemory: | ||
293 | assert(false); // TODO | ||
294 | break; | ||
295 | } | ||
296 | |||
297 | return (Tile)mempool_get_block_index(&iso->tiles, tile); | ||
298 | } | ||
299 | |||
300 | void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) { | ||
301 | assert(iso); | ||
302 | *world_xy_mut(iso, x, y) = tile; | ||
303 | } | ||
304 | |||
305 | void isogfx_pick_tile( | ||
306 | const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) { | ||
307 | assert(iso); | ||
308 | assert(xiso); | ||
309 | assert(yiso); | ||
310 | |||
311 | const vec2 xy_iso = cart2iso( | ||
312 | (vec2){.x = xcart, .y = ycart}, iso->tile_width, iso->tile_height, | ||
313 | iso->screen_width); | ||
314 | |||
315 | const int x = (int)xy_iso.x; | ||
316 | const int y = (int)xy_iso.y; | ||
317 | |||
318 | if ((0 <= x) && (x < iso->world_width) && (0 <= y) && | ||
319 | (y < iso->world_height)) { | ||
320 | *xiso = x; | ||
321 | *yiso = y; | ||
322 | } else { | ||
323 | *xiso = -1; | ||
324 | } | ||
325 | } | ||
326 | |||
327 | void isogfx_render(IsoGfx* iso) { | ||
328 | assert(iso); | ||
329 | draw(iso); | ||
330 | } | ||
331 | |||
332 | void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) { | ||
333 | assert(iso); | ||
334 | assert(x >= 0); | ||
335 | assert(y >= 0); | ||
336 | assert(x < iso->world_width); | ||
337 | assert(y < iso->world_height); | ||
338 | |||
339 | const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0}; | ||
340 | const ivec2 vx = {.x = iso->tile_width / 2, .y = iso->tile_height / 2}; | ||
341 | const ivec2 vy = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2}; | ||
342 | const ivec2 so = | ||
343 | ivec2_add(o, ivec2_add(ivec2_scale(vx, x), ivec2_scale(vy, y))); | ||
344 | |||
345 | draw_tile(iso, so, tile); | ||
346 | } | ||
347 | |||
348 | const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) { | ||
349 | assert(iso); | ||
350 | return iso->screen; | ||
351 | } | ||
352 | |||
353 | int isogfx_world_width(const IsoGfx* iso) { | ||
354 | assert(iso); | ||
355 | return iso->world_width; | ||
356 | } | ||
357 | |||
358 | int isogfx_world_height(const IsoGfx* iso) { | ||
359 | assert(iso); | ||
360 | return iso->world_height; | ||
361 | } | ||
diff --git a/gfx/CMakeLists.txt b/gfx/CMakeLists.txt index f5ef44c..182e4e5 100644 --- a/gfx/CMakeLists.txt +++ b/gfx/CMakeLists.txt | |||
@@ -69,7 +69,8 @@ target_include_directories(gfx PRIVATE | |||
69 | target_compile_options(gfx PRIVATE -std=gnu11 -Wall -Wextra -Wpedantic) | 69 | target_compile_options(gfx PRIVATE -std=gnu11 -Wall -Wextra -Wpedantic) |
70 | 70 | ||
71 | target_link_libraries(gfx PUBLIC | 71 | target_link_libraries(gfx PUBLIC |
72 | cstring) | 72 | cstring |
73 | math) | ||
73 | 74 | ||
74 | target_link_libraries(gfx PRIVATE | 75 | target_link_libraries(gfx PRIVATE |
75 | cgltf | 76 | cgltf |
@@ -78,7 +79,6 @@ target_link_libraries(gfx PRIVATE | |||
78 | glad | 79 | glad |
79 | listpool | 80 | listpool |
80 | log | 81 | log |
81 | math | ||
82 | mempool | 82 | mempool |
83 | shaders | 83 | shaders |
84 | stb | 84 | stb |