aboutsummaryrefslogtreecommitdiff
path: root/gfx-iso
diff options
context:
space:
mode:
Diffstat (limited to 'gfx-iso')
-rw-r--r--gfx-iso/CMakeLists.txt42
-rw-r--r--gfx-iso/demos/CMakeLists.txt2
-rw-r--r--gfx-iso/demos/checkerboard/CMakeLists.txt16
-rw-r--r--gfx-iso/demos/checkerboard/checkerboard.c166
-rw-r--r--gfx-iso/demos/isomap/CMakeLists.txt16
-rw-r--r--gfx-iso/demos/isomap/isomap.c105
-rw-r--r--gfx-iso/include/isogfx/backend.h28
-rw-r--r--gfx-iso/include/isogfx/isogfx.h136
-rw-r--r--gfx-iso/src/backend.c199
-rw-r--r--gfx-iso/src/isogfx.c952
-rw-r--r--gfx-iso/tools/mkasset.py324
11 files changed, 0 insertions, 1986 deletions
diff --git a/gfx-iso/CMakeLists.txt b/gfx-iso/CMakeLists.txt
deleted file mode 100644
index e4a677d..0000000
--- a/gfx-iso/CMakeLists.txt
+++ /dev/null
@@ -1,42 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(isogfx)
4
5set(CMAKE_C_STANDARD 17)
6set(CMAKE_C_STANDARD_REQUIRED On)
7set(CMAKE_C_EXTENSIONS Off)
8
9# isogfx
10
11add_library(isogfx
12 src/isogfx.c)
13
14target_include_directories(isogfx PUBLIC
15 include)
16
17target_link_libraries(isogfx PUBLIC
18 filesystem
19 mem
20 mempool)
21
22target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic)
23
24# Backend
25
26add_library(isogfx-backend
27 src/backend.c)
28
29target_include_directories(isogfx-backend PUBLIC
30 include)
31
32target_link_libraries(isogfx-backend PUBLIC
33 isogfx)
34
35target_link_libraries(isogfx-backend PRIVATE
36 gfx)
37
38target_compile_options(isogfx-backend PRIVATE -Wall -Wextra -Wpedantic)
39
40# Demos
41
42add_subdirectory(demos)
diff --git a/gfx-iso/demos/CMakeLists.txt b/gfx-iso/demos/CMakeLists.txt
deleted file mode 100644
index c0a4101..0000000
--- a/gfx-iso/demos/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
1add_subdirectory(checkerboard)
2add_subdirectory(isomap)
diff --git a/gfx-iso/demos/checkerboard/CMakeLists.txt b/gfx-iso/demos/checkerboard/CMakeLists.txt
deleted file mode 100644
index d1691c6..0000000
--- a/gfx-iso/demos/checkerboard/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(checkerboard)
4
5set(CMAKE_C_STANDARD 17)
6set(CMAKE_C_STANDARD_REQUIRED On)
7set(CMAKE_C_EXTENSIONS Off)
8
9add_executable(checkerboard
10 checkerboard.c)
11
12target_link_libraries(checkerboard PRIVATE
13 gfx-app
14 isogfx-backend)
15
16target_compile_options(checkerboard PRIVATE -Wall -Wextra -Wpedantic)
diff --git a/gfx-iso/demos/checkerboard/checkerboard.c b/gfx-iso/demos/checkerboard/checkerboard.c
deleted file mode 100644
index dbc817c..0000000
--- a/gfx-iso/demos/checkerboard/checkerboard.c
+++ /dev/null
@@ -1,166 +0,0 @@
1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h>
3
4#include <gfx/app.h>
5
6#include <assert.h>
7#include <stdbool.h>
8#include <stdio.h>
9
10static const int WINDOW_WIDTH = 1408;
11static const int WINDOW_HEIGHT = 960;
12static const int MAX_FPS = 60;
13
14// Virtual screen dimensions.
15static const int SCREEN_WIDTH = 704;
16static const int SCREEN_HEIGHT = 480;
17
18static const int TILE_WIDTH = 32;
19static const int TILE_HEIGHT = TILE_WIDTH / 2;
20static const int WORLD_WIDTH = 20;
21static const int WORLD_HEIGHT = 20;
22
23static const TileDesc tile_set[] = {
24 {.type = TileFromColour,
25 .width = TILE_WIDTH,
26 .height = TILE_HEIGHT,
27 .colour = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46, .a = 0xff}},
28 {.type = TileFromColour,
29 .width = TILE_WIDTH,
30 .height = TILE_HEIGHT,
31 .colour = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0, .a = 0xff}},
32 {.type = TileFromColour,
33 .width = TILE_WIDTH,
34 .height = TILE_HEIGHT,
35 .colour = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84, .a = 0xff}},
36};
37
38typedef enum Colour {
39 Black,
40 White,
41 Red,
42} Colour;
43
44typedef struct GfxAppState {
45 IsoBackend* backend;
46 IsoGfx* iso;
47 Tile red;
48 int xpick;
49 int ypick;
50} GfxAppState;
51
52static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) {
53 assert(iso);
54 for (int y = 0; y < isogfx_world_height(iso); ++y) {
55 for (int x = 0; x < isogfx_world_width(iso); ++x) {
56 const int odd_col = x & 1;
57 const int odd_row = y & 1;
58 const Tile value = (odd_row ^ odd_col) == 0 ? black : white;
59 isogfx_set_tile(iso, x, y, value);
60 }
61 }
62}
63
64static bool init(GfxAppState* state, int argc, const char** argv) {
65 assert(state);
66
67 (void)argc;
68 (void)argv;
69
70 if (!(state->iso = isogfx_new(&(IsoGfxDesc){
71 .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) {
72 return false;
73 }
74 IsoGfx* iso = state->iso;
75
76 isogfx_resize(iso, SCREEN_WIDTH, SCREEN_HEIGHT);
77
78 if (!isogfx_make_world(
79 iso, &(WorldDesc){
80 .tile_width = TILE_WIDTH,
81 .tile_height = TILE_HEIGHT,
82 .world_width = WORLD_WIDTH,
83 .world_height = WORLD_HEIGHT})) {
84 return false;
85 }
86
87 const Tile black = isogfx_make_tile(iso, &tile_set[Black]);
88 const Tile white = isogfx_make_tile(iso, &tile_set[White]);
89 state->red = isogfx_make_tile(iso, &tile_set[Red]);
90 make_checkerboard(iso, black, white);
91
92 if (!(state->backend = IsoBackendInit(iso))) {
93 return false;
94 }
95
96 return true;
97}
98
99static void shutdown(GfxAppState* state) {
100 assert(state);
101
102 IsoBackendShutdown(&state->backend);
103 isogfx_del(&state->iso);
104}
105
106static void update(GfxAppState* state, double t, double dt) {
107 assert(state);
108 (void)dt;
109
110 IsoGfx* iso = state->iso;
111
112 isogfx_update(iso, t);
113
114 // Get mouse position in window coordinates.
115 double mouse_x, mouse_y;
116 gfx_app_get_mouse_position(&mouse_x, &mouse_y);
117
118 // Map from window coordinates to virtual screen coordinates.
119 IsoBackendGetMousePosition(
120 state->backend, mouse_x, mouse_y, &mouse_x, &mouse_y);
121
122 isogfx_pick_tile(iso, mouse_x, mouse_y, &state->xpick, &state->ypick);
123
124 printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick);
125}
126
127static void render(GfxAppState* state) {
128 assert(state);
129
130 IsoGfx* iso = state->iso;
131
132 isogfx_render(iso);
133
134 if ((state->xpick != -1) && (state->ypick != -1)) {
135 isogfx_draw_tile(iso, state->xpick, state->ypick, state->red);
136 }
137
138 IsoBackendRender(state->backend, iso);
139}
140
141static void resize(GfxAppState* state, int width, int height) {
142 assert(state);
143
144 IsoBackendResizeWindow(state->backend, state->iso, width, height);
145}
146
147int main(int argc, const char** argv) {
148 GfxAppState state = {0};
149 gfx_app_run(
150 &(GfxAppDesc){
151 .argc = argc,
152 .argv = argv,
153 .width = WINDOW_WIDTH,
154 .height = WINDOW_HEIGHT,
155 .max_fps = MAX_FPS,
156 .update_delta_time = MAX_FPS > 0 ? 1.0 / (double)MAX_FPS : 0.0,
157 .title = "Isometric Renderer",
158 .app_state = &state},
159 &(GfxAppCallbacks){
160 .init = init,
161 .update = update,
162 .render = render,
163 .resize = resize,
164 .shutdown = shutdown});
165 return 0;
166}
diff --git a/gfx-iso/demos/isomap/CMakeLists.txt b/gfx-iso/demos/isomap/CMakeLists.txt
deleted file mode 100644
index 2dbfd32..0000000
--- a/gfx-iso/demos/isomap/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(isomap)
4
5set(CMAKE_C_STANDARD 17)
6set(CMAKE_C_STANDARD_REQUIRED On)
7set(CMAKE_C_EXTENSIONS Off)
8
9add_executable(isomap
10 isomap.c)
11
12target_link_libraries(isomap PRIVATE
13 gfx-app
14 isogfx-backend)
15
16target_compile_options(isomap PRIVATE -Wall -Wextra -Wpedantic)
diff --git a/gfx-iso/demos/isomap/isomap.c b/gfx-iso/demos/isomap/isomap.c
deleted file mode 100644
index a233659..0000000
--- a/gfx-iso/demos/isomap/isomap.c
+++ /dev/null
@@ -1,105 +0,0 @@
1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h>
3
4#include <gfx/app.h>
5
6#include <assert.h>
7#include <stdbool.h>
8
9static const int WINDOW_WIDTH = 1408;
10static const int WINDOW_HEIGHT = 960;
11static const int MAX_FPS = 60;
12
13// Virtual screen dimensions.
14static const int SCREEN_WIDTH = 704;
15static const int SCREEN_HEIGHT = 480;
16
17typedef struct GfxAppState {
18 IsoBackend* backend;
19 IsoGfx* iso;
20 int xpick;
21 int ypick;
22 SpriteSheet stag_sheet;
23 Sprite stag;
24} GfxAppState;
25
26static bool init(GfxAppState* state, int argc, const char** argv) {
27 assert(state);
28 (void)argc;
29 (void)argv;
30
31 if (!(state->iso = isogfx_new(&(IsoGfxDesc){
32 .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) {
33 return false;
34 }
35 IsoGfx* iso = state->iso;
36
37 isogfx_resize(iso, SCREEN_WIDTH, SCREEN_HEIGHT);
38
39 if (!isogfx_load_world(iso, "/home/jeanne/assets/tilemaps/demo1.tm")) {
40 return false;
41 }
42
43 if (!isogfx_load_sprite_sheet(
44 iso, "/home/jeanne/assets/tilesets/scrabling/critters/stag/stag.ss",
45 &state->stag_sheet)) {
46 return false;
47 }
48
49 state->stag = isogfx_make_sprite(iso, state->stag_sheet);
50 isogfx_set_sprite_position(iso, state->stag, 5, 4);
51
52 if (!(state->backend = IsoBackendInit(iso))) {
53 return false;
54 }
55
56 return true;
57}
58
59static void shutdown(GfxAppState* state) {
60 assert(state);
61 //
62}
63
64static void update(GfxAppState* state, double t, double dt) {
65 assert(state);
66 (void)dt;
67
68 IsoGfx* iso = state->iso;
69 isogfx_update(iso, t);
70}
71
72static void render(GfxAppState* state) {
73 assert(state);
74
75 IsoGfx* iso = state->iso;
76 isogfx_render(iso);
77 IsoBackendRender(state->backend, iso);
78}
79
80static void resize(GfxAppState* state, int width, int height) {
81 assert(state);
82
83 IsoBackendResizeWindow(state->backend, state->iso, width, height);
84}
85
86int main(int argc, const char** argv) {
87 GfxAppState state = {0};
88 gfx_app_run(
89 &(GfxAppDesc){
90 .argc = argc,
91 .argv = argv,
92 .width = WINDOW_WIDTH,
93 .height = WINDOW_HEIGHT,
94 .max_fps = MAX_FPS,
95 .update_delta_time = MAX_FPS > 0 ? 1.0 / (double)MAX_FPS : 0.0,
96 .title = "Isometric Renderer",
97 .app_state = &state},
98 &(GfxAppCallbacks){
99 .init = init,
100 .update = update,
101 .render = render,
102 .resize = resize,
103 .shutdown = shutdown});
104 return 0;
105}
diff --git a/gfx-iso/include/isogfx/backend.h b/gfx-iso/include/isogfx/backend.h
deleted file mode 100644
index 172991d..0000000
--- a/gfx-iso/include/isogfx/backend.h
+++ /dev/null
@@ -1,28 +0,0 @@
1#pragma once
2
3#include <stdbool.h>
4
5typedef struct Gfx Gfx;
6typedef struct IsoGfx IsoGfx;
7
8typedef struct IsoBackend IsoBackend;
9
10/// Initialize the backend.
11IsoBackend* IsoBackendInit(const IsoGfx*);
12
13/// Shut down the backend.
14void IsoBackendShutdown(IsoBackend**);
15
16/// Notify the backend of a window resize event.
17/// This allows the backend to determine how to position and scale the iso
18/// screen buffer on the graphics window.
19void IsoBackendResizeWindow(IsoBackend*, const IsoGfx*, int width, int height);
20
21/// Render the iso screen to the graphics window.
22void IsoBackendRender(const IsoBackend*, const IsoGfx*);
23
24/// Map window coordinates to iso space coordinates.
25/// This takes into account any possible resizing done by the backend in
26/// response to calls to IsoBackendResizeWindow().
27bool IsoBackendGetMousePosition(
28 const IsoBackend*, double window_x, double window_y, double* x, double* y);
diff --git a/gfx-iso/include/isogfx/isogfx.h b/gfx-iso/include/isogfx/isogfx.h
deleted file mode 100644
index 3421a7b..0000000
--- a/gfx-iso/include/isogfx/isogfx.h
+++ /dev/null
@@ -1,136 +0,0 @@
1/*
2 * Isometric rendering engine.
3 */
4#pragma once
5
6#include <stdbool.h>
7#include <stdint.h>
8
9typedef struct IsoGfx IsoGfx;
10
11/// Sprite sheet handle.
12typedef uint16_t SpriteSheet;
13
14/// Sprite handle.
15typedef uint16_t Sprite;
16
17/// Tile handle.
18typedef uint16_t Tile;
19
20/// Colour channel.
21typedef uint8_t Channel;
22
23typedef struct Pixel {
24 Channel r, g, b, a;
25} Pixel;
26
27typedef enum TileDescType {
28 TileFromColour,
29 TileFromFile,
30 TileFromMemory,
31} TileDescType;
32
33typedef struct TileDesc {
34 TileDescType type;
35 int width; /// Tile width in pixels.
36 int height; /// Tile height in pixels.
37 union {
38 Pixel colour; /// Constant colour tile.
39 struct {
40 const char* path;
41 } file;
42 struct {
43 const uint8_t* data; /// sizeof(Pixel) * width * height
44 } mem;
45 };
46} TileDesc;
47
48typedef struct WorldDesc {
49 int tile_width; /// Base tile width in pixels.
50 int tile_height; /// Base tile height in pixels.
51 int world_width; /// World width in tiles.
52 int world_height; /// World height in tiles.
53 int max_num_tiles; /// 0 for an implementation-defined default.
54} WorldDesc;
55
56typedef struct IsoGfxDesc {
57 int screen_width; /// Screen width in pixels.
58 int screen_height; /// Screen height in pixels.
59 int max_num_sprites; /// 0 for an implementation-defined default.
60 int sprite_sheet_pool_size_bytes; /// 0 for an implementation-defined default.
61} IsoGfxDesc;
62
63/// Create a new isometric graphics engine.
64IsoGfx* isogfx_new(const IsoGfxDesc*);
65
66/// Destroy the isometric graphics engine.
67void isogfx_del(IsoGfx**);
68
69/// Create an empty world.
70bool isogfx_make_world(IsoGfx*, const WorldDesc*);
71
72/// Load a world from a tile map (.TM) file.
73bool isogfx_load_world(IsoGfx*, const char* filepath);
74
75/// Return the world's width.
76int isogfx_world_width(const IsoGfx*);
77
78/// Return the world's height.
79int isogfx_world_height(const IsoGfx*);
80
81/// Create a new tile.
82Tile isogfx_make_tile(IsoGfx*, const TileDesc*);
83
84/// Set the tile at position (x,y).
85void isogfx_set_tile(IsoGfx*, int x, int y, Tile);
86
87/// Set the tiles in positions in the range (x0,y0) - (x1,y1).
88void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile);
89
90/// Load a sprite sheet (.SS) file.
91bool isogfx_load_sprite_sheet(IsoGfx*, const char* filepath, SpriteSheet*);
92
93/// Create an animated sprite.
94Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet);
95
96/// Destroy the sprite.
97void isogfx_del_sprite(IsoGfx*, Sprite);
98
99/// Destroy all the sprites.
100void isogfx_del_sprites(IsoGfx*);
101
102/// Set the sprite's position.
103void isogfx_set_sprite_position(IsoGfx*, Sprite, int x, int y);
104
105/// Set the sprite's current animation.
106void isogfx_set_sprite_animation(IsoGfx*, Sprite, int animation);
107
108/// Update the renderer.
109///
110/// Currently this updates the sprite animations.
111void isogfx_update(IsoGfx*, double t);
112
113/// Render the world.
114void isogfx_render(IsoGfx*);
115
116/// Draw/overlay a tile at position (x,y).
117///
118/// This function just renders a tile at position (x,y) and should be called
119/// after isogfx_render() to obtain the correct result. To set the tile at
120/// position (x,y) instead, use isogfx_set_tile().
121void isogfx_draw_tile(IsoGfx*, int x, int y, Tile);
122
123/// Resize the virtual screen's dimensions.
124bool isogfx_resize(IsoGfx*, int screen_width, int screen_height);
125
126/// Get the virtual screen's dimensions.
127void isogfx_get_screen_size(const IsoGfx*, int* width, int* height);
128
129/// Return a pointer to the virtual screen's colour buffer.
130///
131/// Call after each call to isogfx_render() to retrieve the render output.
132const Pixel* isogfx_get_screen_buffer(const IsoGfx*);
133
134/// Translate Cartesian to isometric coordinates.
135void isogfx_pick_tile(
136 const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso);
diff --git a/gfx-iso/src/backend.c b/gfx-iso/src/backend.c
deleted file mode 100644
index db91647..0000000
--- a/gfx-iso/src/backend.c
+++ /dev/null
@@ -1,199 +0,0 @@
1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h>
3
4#include <gfx/core.h>
5#include <gfx/gfx.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 <stdlib.h>
13
14typedef struct IsoBackend {
15 Gfx* gfx;
16 Scene* scene;
17 /// The screen or "iso screen" refers to the colour buffer of the iso graphics
18 /// library. This texture is used to draw the iso screen onto the graphics
19 /// window.
20 Texture* screen_texture;
21 /// Window size.
22 int window_width;
23 int window_height;
24 /// The viewport refers to the area inside the window to which screen_texture
25 /// is drawn. It is a scaled version of the iso screen, scaled while
26 /// respecting the iso screen's aspect ratio to prevent distortion.
27 int viewport_x, viewport_y, viewport_width, viewport_height;
28 double stretch; // Stretch factor from iso screen dimensions to viewport
29 // dimensions.
30} IsoBackend;
31
32IsoBackend* IsoBackendInit(const IsoGfx* iso) {
33 assert(iso);
34
35 IsoBackend* backend = calloc(1, sizeof(IsoBackend));
36 if (!backend) {
37 return 0;
38 }
39
40 if (!(backend->gfx = gfx_init())) {
41 goto cleanup;
42 }
43 GfxCore* gfxcore = gfx_get_core(backend->gfx);
44
45 int screen_width, screen_height;
46 isogfx_get_screen_size(iso, &screen_width, &screen_height);
47
48 if (!(backend->screen_texture = gfx_make_texture(
49 gfxcore, &(TextureDesc){
50 .width = screen_width,
51 .height = screen_height,
52 .dimension = Texture2D,
53 .format = TextureSRGBA8,
54 .filtering = NearestFiltering,
55 .wrap = ClampToEdge,
56 .mipmaps = false}))) {
57 goto cleanup;
58 }
59
60 ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore);
61 if (!shader) {
62 goto cleanup;
63 }
64
65 Geometry* geometry = gfx_make_quad_11(gfxcore);
66 if (!geometry) {
67 goto cleanup;
68 }
69
70 MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1};
71 material_desc.uniforms[0] = (ShaderUniform){
72 .type = UniformTexture,
73 .value.texture = backend->screen_texture,
74 .name = sstring_make("Texture")};
75 Material* material = gfx_make_material(&material_desc);
76 if (!material) {
77 return false;
78 }
79
80 const MeshDesc mesh_desc =
81 (MeshDesc){.geometry = geometry, .material = material, .shader = shader};
82 Mesh* mesh = gfx_make_mesh(&mesh_desc);
83 if (!mesh) {
84 goto cleanup;
85 }
86
87 SceneObject* object =
88 gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}});
89 if (!object) {
90 goto cleanup;
91 }
92
93 backend->scene = gfx_make_scene();
94 SceneNode* node = gfx_make_object_node(object);
95 SceneNode* root = gfx_get_scene_root(backend->scene);
96 gfx_set_node_parent(node, root);
97
98 return backend;
99
100cleanup:
101 if (backend->gfx) {
102 gfx_destroy(&backend->gfx);
103 }
104 free(backend);
105 return 0;
106}
107
108void IsoBackendShutdown(IsoBackend** ppApp) {
109 assert(ppApp);
110
111 IsoBackend* app = *ppApp;
112 if (!app) {
113 return;
114 }
115
116 gfx_destroy(&app->gfx);
117}
118
119void IsoBackendResizeWindow(
120 IsoBackend* app, const IsoGfx* iso, int width, int height) {
121 assert(app);
122 assert(iso);
123
124 app->window_width = width;
125 app->window_height = height;
126
127 // Virtual screen dimensions.
128 int screen_width, screen_height;
129 isogfx_get_screen_size(iso, &screen_width, &screen_height);
130
131 // Stretch the virtual screen onto the viewport while respecting the screen's
132 // aspect ratio to prevent distortion.
133 if (width > height) { // Wide screen.
134 app->stretch = (double)height / (double)screen_height;
135 app->viewport_width = (int)((double)screen_width * app->stretch);
136 app->viewport_height = height;
137 app->viewport_x = (width - app->viewport_width) / 2;
138 app->viewport_y = 0;
139 } else { // Tall screen.
140 app->stretch = (double)width / (double)screen_width;
141 app->viewport_width = width;
142 app->viewport_height = (int)((float)screen_height * app->stretch);
143 app->viewport_x = 0;
144 app->viewport_y = (height - app->viewport_height) / 2;
145 }
146}
147
148void IsoBackendRender(const IsoBackend* app, const IsoGfx* iso) {
149 assert(app);
150 assert(iso);
151
152 const Pixel* screen = isogfx_get_screen_buffer(iso);
153 assert(screen);
154 gfx_update_texture(app->screen_texture, &(TextureDataDesc){.pixels = screen});
155
156 GfxCore* gfxcore = gfx_get_core(app->gfx);
157 Renderer* renderer = gfx_get_renderer(app->gfx);
158
159 // Clear the whole window.
160 gfx_set_viewport(gfxcore, 0, 0, app->window_width, app->window_height);
161 gfx_clear(gfxcore, vec4_make(0, 0, 0, 0));
162
163 // Draw to the subregion where the virtual screen can stretch without
164 // distortion.
165 gfx_set_viewport(
166 gfxcore, app->viewport_x, app->viewport_y, app->viewport_width,
167 app->viewport_height);
168
169 // Render the iso screen.
170 gfx_start_frame(gfxcore);
171 gfx_render_scene(
172 renderer, &(RenderSceneParams){
173 .mode = RenderDefault, .scene = app->scene, .camera = 0});
174 gfx_end_frame(gfxcore);
175}
176
177bool IsoBackendGetMousePosition(
178 const IsoBackend* app, double window_x, double window_y, double* x,
179 double* y) {
180 assert(app);
181
182 // Translate from window coordinates to the subregion where the stretched
183 // iso screen is rendered.
184 const double screen_x = window_x - app->viewport_x;
185 const double screen_y = window_y - app->viewport_y;
186
187 // Position may be out of bounds.
188 if ((0 <= screen_x) && (screen_x < app->viewport_width) && (0 <= screen_y) &&
189 (screen_y < app->viewport_height)) {
190 // Scale back from the stretched subregion to the iso screen dimensions.
191 *x = screen_x / app->stretch;
192 *y = screen_y / app->stretch;
193 return true;
194 } else {
195 *x = -1;
196 *y = -1;
197 return false;
198 }
199}
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c
deleted file mode 100644
index 52c4ae2..0000000
--- a/gfx-iso/src/isogfx.c
+++ /dev/null
@@ -1,952 +0,0 @@
1#include <isogfx/isogfx.h>
2
3#include <filesystem.h>
4#include <mem.h>
5#include <mempool.h>
6#include <path.h>
7
8#include <linux/limits.h>
9
10#include <assert.h>
11#include <stdbool.h>
12#include <stdint.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16
17/// Maximum number of tiles unless the user specifies a value.
18#define DEFAULT_MAX_NUM_TILES 1024
19
20/// Maximum number of sprites unless the user specifies a value.
21#define DEFAULT_MAX_NUM_SPRITES 128
22
23/// Size of sprite sheet pool in bytes unless the user specifies a value.
24#define DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES (8 * 1024 * 1024)
25
26/// Default animation speed.
27#define ANIMATION_FPS 10
28
29/// Time between animation updates.
30#define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS)
31
32typedef struct ivec2 {
33 int x, y;
34} ivec2;
35
36typedef struct vec2 {
37 double x, y;
38} vec2;
39
40// -----------------------------------------------------------------------------
41// Tile set (TS) and tile map (TM) file formats.
42// -----------------------------------------------------------------------------
43
44/// Maximum length of path strings in .TS and .TM files.
45#define MAX_PATH_LENGTH 128
46
47typedef struct Ts_Tile {
48 uint16_t width; /// Tile width in pixels.
49 uint16_t height; /// Tile height in pixels.
50 Pixel pixels[1]; /// Count: width * height.
51} Ts_Tile;
52
53typedef struct Ts_TileSet {
54 uint16_t num_tiles;
55 uint16_t max_tile_width; /// Maximum tile width in pixels.
56 uint16_t max_tile_height; /// Maximum tile height in pixels.
57 Ts_Tile tiles[1]; /// Count: num_tiles.
58} Ts_TileSet;
59
60typedef struct Tm_Layer {
61 union {
62 char tileset_path[MAX_PATH_LENGTH]; // Relative to the Tm_Map file.
63 };
64 Tile tiles[1]; /// Count: world_width * world_height.
65} Tm_Layer;
66
67typedef struct Tm_Map {
68 uint16_t world_width; /// World width in number of tiles.
69 uint16_t world_height; /// World height in number of tiles.
70 uint16_t base_tile_width;
71 uint16_t base_tile_height;
72 uint16_t num_layers;
73 Tm_Layer layers[1]; // Count: num_layers.
74} Tm_Map;
75
76static inline const Tm_Layer* tm_map_get_next_layer(
77 const Tm_Map* map, const Tm_Layer* layer) {
78 assert(map);
79 assert(layer);
80 return (const Tm_Layer*)((const uint8_t*)layer + sizeof(Tm_Layer) +
81 ((map->world_width * map->world_height - 1) *
82 sizeof(Tile)));
83}
84
85static inline const Ts_Tile* ts_tileset_get_next_tile(
86 const Ts_TileSet* tileset, const Ts_Tile* tile) {
87 assert(tileset);
88 assert(tile);
89 return (const Ts_Tile*)((const uint8_t*)tile + sizeof(Ts_Tile) +
90 ((tile->width * tile->height - 1) * sizeof(Pixel)));
91}
92
93// -----------------------------------------------------------------------------
94// Sprite sheet file format.
95// -----------------------------------------------------------------------------
96
97/// A row of sprites in a sprite sheet.
98///
99/// Each row in a sprite sheet can have a different number of columns.
100///
101/// The pixels of the row follow a "sprite-major" order. It contains the
102/// 'sprite_width * sprite_height' pixels for the first column/sprite, then the
103/// second column/sprite, etc.
104///
105/// Pixels are 8-bit indices into the sprite sheet's colour palette.
106typedef struct Ss_Row {
107 uint16_t num_cols; /// Number of columns in this row.
108 uint8_t pixels[1]; /// Count: num_cols * sprite_width * sprite_height.
109} Ss_Row;
110
111typedef struct Ss_Palette {
112 uint16_t num_colours;
113 Pixel colours[1]; /// Count: num_colors.
114} Ss_Palette;
115
116/// Sprite sheet top-level data definition.
117///
118/// Sprite width and height are assumed constant throughout the sprite sheet.
119typedef struct Ss_SpriteSheet {
120 uint16_t sprite_width; /// Sprite width in pixels.
121 uint16_t sprite_height; /// Sprite height in pixels.
122 uint16_t num_rows;
123 Ss_Palette palette; /// Variable size.
124 Ss_Row rows[1]; /// Count: num_rows. Variable offset.
125} Ss_SpriteSheet;
126
127static inline const Ss_Row* get_sprite_sheet_row(
128 const Ss_SpriteSheet* sheet, int row) {
129 assert(sheet);
130 assert(row >= 0);
131 assert(row < sheet->num_rows);
132 // Skip over the palette.
133 const Ss_Row* rows =
134 (const Ss_Row*)(&sheet->palette.colours[0] + sheet->palette.num_colours);
135 return &rows[row];
136}
137
138static inline const uint8_t* get_sprite_sheet_sprite(
139 const Ss_SpriteSheet* sheet, const Ss_Row* row, int col) {
140 assert(sheet);
141 assert(row);
142 assert(col >= 0);
143 assert(col < row->num_cols);
144 const int sprite_offset = col * sheet->sprite_width * sheet->sprite_height;
145 const uint8_t* sprite = &row->pixels[sprite_offset];
146 return sprite;
147}
148
149// -----------------------------------------------------------------------------
150// Renderer state.
151// -----------------------------------------------------------------------------
152
153typedef struct TileData {
154 uint16_t width;
155 uint16_t height;
156 uint16_t pixels_handle; // Handle to the tile's pixels in the pixel pool.
157} TileData;
158
159// File format is already convenient for working in memory.
160typedef Ss_Row SpriteSheetRow;
161typedef Ss_SpriteSheet SpriteSheetData;
162
163typedef struct SpriteData {
164 SpriteSheet sheet; // Handle to the sprite's sheet.
165 ivec2 position;
166 int animation; // Current animation.
167 int frame; // Current frame of animation.
168} SpriteData;
169
170DEF_MEMPOOL_DYN(TilePool, TileData)
171DEF_MEM_DYN(PixelPool, Pixel)
172
173DEF_MEMPOOL_DYN(SpritePool, SpriteData)
174DEF_MEM_DYN(SpriteSheetPool, SpriteSheetData)
175
176typedef struct IsoGfx {
177 int screen_width;
178 int screen_height;
179 int tile_width;
180 int tile_height;
181 int world_width;
182 int world_height;
183 int max_num_sprites;
184 int sprite_sheet_pool_size_bytes;
185 double last_animation_time;
186 Tile* world;
187 Pixel* screen;
188 TilePool tiles;
189 PixelPool pixels;
190 SpritePool sprites;
191 SpriteSheetPool sheets;
192} IsoGfx;
193
194// -----------------------------------------------------------------------------
195// Math and world / tile / screen access.
196// -----------------------------------------------------------------------------
197
198static inline ivec2 ivec2_add(ivec2 a, ivec2 b) {
199 return (ivec2){.x = a.x + b.x, .y = a.y + b.y};
200}
201
202static inline ivec2 ivec2_scale(ivec2 a, int s) {
203 return (ivec2){.x = a.x * s, .y = a.y * s};
204}
205
206static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) {
207 return (ivec2){
208 .x = (iso.x - iso.y) * (s / 2) + (w / 2), .y = (iso.x + iso.y) * (t / 2)};
209}
210
211// Method 1.
212// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
213// const double x = cart.x - (double)(w / 2);
214// const double xiso = (x * t + cart.y * s) / (double)(s * t);
215// return (vec2){
216// .x = (int)(xiso), .y = (int)((2.0 / (double)t) * cart.y - xiso)};
217//}
218
219// Method 2.
220static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
221 const double one_over_s = 1. / (double)s;
222 const double one_over_t = 1. / (double)t;
223 const double x = cart.x - (double)(w / 2);
224 return (vec2){
225 .x = (one_over_s * x + one_over_t * cart.y),
226 .y = (-one_over_s * x + one_over_t * cart.y)};
227}
228
229static const Pixel* tile_xy_const_ref(
230 const IsoGfx* iso, const TileData* tile, int x, int y) {
231 assert(iso);
232 assert(tile);
233 assert(x >= 0);
234 assert(y >= 0);
235 assert(x < tile->width);
236 assert(y < tile->height);
237 return &mem_get_chunk(&iso->pixels, tile->pixels_handle)[y * tile->width + x];
238}
239
240// static Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) {
241// return *tile_xy_const_ref(iso, tile, x, y);
242// }
243
244static Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) {
245 return (Pixel*)tile_xy_const_ref(iso, tile, x, y);
246}
247
248static inline const Tile* world_xy_const_ref(const IsoGfx* iso, int x, int y) {
249 assert(iso);
250 assert(x >= 0);
251 assert(y >= 0);
252 assert(x < iso->world_width);
253 assert(y < iso->world_height);
254 return &iso->world[y * iso->world_width + x];
255}
256
257static inline Tile world_xy(const IsoGfx* iso, int x, int y) {
258 return *world_xy_const_ref(iso, x, y);
259}
260
261static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
262 return (Tile*)world_xy_const_ref(iso, x, y);
263}
264
265static inline const Pixel* screen_xy_const_ref(
266 const IsoGfx* iso, int x, int y) {
267 assert(iso);
268 assert(x >= 0);
269 assert(y >= 0);
270 assert(x < iso->screen_width);
271 assert(y < iso->screen_height);
272 return &iso->screen[y * iso->screen_width + x];
273}
274
275static inline Pixel screen_xy(IsoGfx* iso, int x, int y) {
276 return *screen_xy_const_ref(iso, x, y);
277}
278
279static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) {
280 return (Pixel*)screen_xy_const_ref(iso, x, y);
281}
282
283static int calc_num_tile_blocks(
284 int base_tile_width, int base_tile_height, int tile_width,
285 int tile_height) {
286 const int base_tile_size = base_tile_width * base_tile_height;
287 const int tile_size = tile_width * tile_height;
288 const int num_blocks = tile_size / base_tile_size;
289 return num_blocks;
290}
291
292// -----------------------------------------------------------------------------
293// Renderer, world and tile management.
294// -----------------------------------------------------------------------------
295
296IsoGfx* isogfx_new(const IsoGfxDesc* desc) {
297 assert(desc->screen_width > 0);
298 assert(desc->screen_height > 0);
299 // Part of our implementation assumes even widths and heights for precision.
300 assert((desc->screen_width & 1) == 0);
301 assert((desc->screen_height & 1) == 0);
302
303 IsoGfx* iso = calloc(1, sizeof(IsoGfx));
304 if (!iso) {
305 return 0;
306 }
307
308 iso->screen_width = desc->screen_width;
309 iso->screen_height = desc->screen_height;
310
311 iso->last_animation_time = 0.0;
312
313 iso->max_num_sprites = desc->max_num_sprites == 0 ? DEFAULT_MAX_NUM_SPRITES
314 : desc->max_num_sprites;
315 iso->sprite_sheet_pool_size_bytes = desc->sprite_sheet_pool_size_bytes == 0
316 ? DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES
317 : desc->sprite_sheet_pool_size_bytes;
318
319 const int screen_size = desc->screen_width * desc->screen_height;
320 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) {
321 goto cleanup;
322 }
323
324 return iso;
325
326cleanup:
327 isogfx_del(&iso);
328 return 0;
329}
330
331/// Destroy the world, its tile set, and the underlying pools.
332static void destroy_world(IsoGfx* iso) {
333 assert(iso);
334 if (iso->world) {
335 free(iso->world);
336 iso->world = 0;
337 }
338 mempool_del(&iso->tiles);
339 mem_del(&iso->pixels);
340}
341
342/// Destroy all loaded sprites and the underlying pools.
343static void destroy_sprites(IsoGfx* iso) {
344 assert(iso);
345 mempool_del(&iso->sprites);
346 mem_del(&iso->sheets);
347}
348
349void isogfx_del(IsoGfx** pIso) {
350 assert(pIso);
351 IsoGfx* iso = *pIso;
352 if (iso) {
353 destroy_world(iso);
354 destroy_sprites(iso);
355 if (iso->screen) {
356 free(iso->screen);
357 iso->screen = 0;
358 }
359 free(iso);
360 *pIso = 0;
361 }
362}
363
364bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) {
365 assert(iso);
366 assert(desc);
367 assert(desc->tile_width > 0);
368 assert(desc->tile_height > 0);
369 // Part of our implementation assumes even widths and heights for greater
370 // precision.
371 assert((desc->tile_width & 1) == 0);
372 assert((desc->tile_height & 1) == 0);
373
374 // Handle recreation by destroying the previous world.
375 destroy_world(iso);
376
377 iso->tile_width = desc->tile_width;
378 iso->tile_height = desc->tile_height;
379 iso->world_width = desc->world_width;
380 iso->world_height = desc->world_height;
381
382 const int world_size = desc->world_width * desc->world_height;
383 const int tile_size = desc->tile_width * desc->tile_height;
384 const int tile_size_bytes = tile_size * (int)sizeof(Pixel);
385 const int tile_pool_size =
386 desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES;
387
388 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
389 goto cleanup;
390 }
391 if (!mempool_make_dyn(&iso->tiles, world_size, sizeof(TileData))) {
392 goto cleanup;
393 }
394 if (!mem_make_dyn(&iso->pixels, tile_pool_size, tile_size_bytes)) {
395 goto cleanup;
396 }
397
398 return true;
399
400cleanup:
401 destroy_world(iso);
402 return false;
403}
404
405bool isogfx_load_world(IsoGfx* iso, const char* filepath) {
406 assert(iso);
407 assert(filepath);
408
409 bool success = false;
410
411 // Handle recreation by destroying the previous world.
412 destroy_world(iso);
413
414 // Load the map.
415 printf("Load tile map: %s\n", filepath);
416 Tm_Map* map = read_file(filepath);
417 if (!map) {
418 goto cleanup;
419 }
420
421 // Allocate memory for the map and tile sets.
422 const int world_size = map->world_width * map->world_height;
423 const int base_tile_size = map->base_tile_width * map->base_tile_height;
424 const int base_tile_size_bytes = base_tile_size * (int)sizeof(Pixel);
425 // TODO: Need to get the total number of tiles from the map.
426 const int tile_pool_size = DEFAULT_MAX_NUM_TILES;
427
428 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
429 goto cleanup;
430 }
431 if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) {
432 goto cleanup;
433 }
434 if (!mem_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) {
435 goto cleanup;
436 }
437
438 // Load the tile sets.
439 const Tm_Layer* layer = &map->layers[0];
440 // TODO: Handle num_layers layers.
441 for (int i = 0; i < 1; ++i) {
442 const char* ts_path = layer->tileset_path;
443
444 // Tile set path is relative to the tile map file. Make it relative to the
445 // current working directory before loading.
446 char ts_path_cwd[PATH_MAX] = {0};
447 if (!path_make_relative(filepath, ts_path, ts_path_cwd, PATH_MAX)) {
448 goto cleanup;
449 }
450
451 Ts_TileSet* tileset = read_file(ts_path_cwd);
452 if (!tileset) {
453 goto cleanup;
454 };
455
456 // Load tile data.
457 const Ts_Tile* tile = &tileset->tiles[0];
458 for (uint16_t j = 0; j < tileset->num_tiles; ++j) {
459 // Tile dimensions should be a multiple of the base tile size.
460 assert((tile->width % map->base_tile_width) == 0);
461 assert((tile->height % map->base_tile_height) == 0);
462
463 // Allocate N base tile size blocks for the tile.
464 const uint16_t tile_size = tile->width * tile->height;
465 const int num_blocks = tile_size / base_tile_size;
466 Pixel* pixels = mem_alloc(&iso->pixels, num_blocks);
467 assert(pixels);
468 memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel));
469
470 // Allocate the tile data.
471 TileData* tile_data = mempool_alloc(&iso->tiles);
472 assert(tile_data);
473 tile_data->width = tile->width;
474 tile_data->height = tile->height;
475 tile_data->pixels_handle =
476 (uint16_t)mem_get_chunk_handle(&iso->pixels, pixels);
477
478 tile = ts_tileset_get_next_tile(tileset, tile);
479 }
480
481 printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd);
482
483 free(tileset);
484 layer = tm_map_get_next_layer(map, layer);
485 }
486
487 // Load the map into the world.
488 layer = &map->layers[0];
489 // TODO: Handle num_layers layers.
490 for (int i = 0; i < 1; ++i) {
491 memcpy(iso->world, layer->tiles, world_size * sizeof(Tile));
492
493 // TODO: We need to handle 'firsgid' in TMX files.
494 for (int j = 0; j < world_size; ++j) {
495 iso->world[j] -= 1;
496 }
497
498 layer = tm_map_get_next_layer(map, layer);
499 }
500
501 iso->world_width = map->world_width;
502 iso->world_height = map->world_height;
503 iso->tile_width = map->base_tile_width;
504 iso->tile_height = map->base_tile_height;
505
506 success = true;
507
508cleanup:
509 if (map) {
510 free(map);
511 }
512 if (!success) {
513 destroy_world(iso);
514 }
515 return success;
516}
517
518int isogfx_world_width(const IsoGfx* iso) {
519 assert(iso);
520 return iso->world_width;
521}
522
523int isogfx_world_height(const IsoGfx* iso) {
524 assert(iso);
525 return iso->world_height;
526}
527
528/// Create a tile mask procedurally.
529static void make_tile_from_colour(
530 const IsoGfx* iso, Pixel colour, TileData* tile) {
531 assert(iso);
532 assert(tile);
533
534 const int width = tile->width;
535 const int height = tile->height;
536 const int r = width / height;
537
538 for (int y = 0; y < height / 2; ++y) {
539 const int mask_start = width / 2 - r * y - 1;
540 const int mask_end = width / 2 + r * y + 1;
541 for (int x = 0; x < width; ++x) {
542 const bool mask = (mask_start <= x) && (x <= mask_end);
543 const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0};
544
545 // Top half.
546 *tile_xy_mut(iso, tile, x, y) = val;
547
548 // Bottom half reflects the top half.
549 const int y_reflected = height - y - 1;
550 *tile_xy_mut(iso, tile, x, y_reflected) = val;
551 }
552 }
553}
554
555Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
556 assert(iso);
557 assert(desc);
558 // Client must create world before creating tiles.
559 assert(iso->tile_width > 0);
560 assert(iso->tile_height > 0);
561
562 TileData* tile = mempool_alloc(&iso->tiles);
563 assert(tile); // TODO: Make this a hard assert.
564
565 const int num_blocks = calc_num_tile_blocks(
566 iso->tile_width, iso->tile_height, desc->width, desc->height);
567
568 Pixel* pixels = mem_alloc(&iso->pixels, num_blocks);
569 assert(pixels); // TODO: Make this a hard assert.
570
571 tile->width = desc->width;
572 tile->height = desc->height;
573 tile->pixels_handle = mem_get_chunk_handle(&iso->pixels, pixels);
574
575 switch (desc->type) {
576 case TileFromColour:
577 make_tile_from_colour(iso, desc->colour, tile);
578 break;
579 case TileFromFile:
580 assert(false); // TODO
581 break;
582 case TileFromMemory:
583 assert(false); // TODO
584 break;
585 }
586
587 return (Tile)mempool_get_block_index(&iso->tiles, tile);
588}
589
590void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) {
591 assert(iso);
592 *world_xy_mut(iso, x, y) = tile;
593}
594
595void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) {
596 assert(iso);
597 for (int y = y0; y < y1; ++y) {
598 for (int x = x0; x < x1; ++x) {
599 isogfx_set_tile(iso, x, y, tile);
600 }
601 }
602}
603
604bool isogfx_load_sprite_sheet(
605 IsoGfx* iso, const char* filepath, SpriteSheet* p_sheet) {
606 assert(iso);
607 assert(filepath);
608 assert(p_sheet);
609
610 bool success = false;
611
612 // Lazy initialization of sprite pools.
613 if (mempool_capacity(&iso->sprites) == 0) {
614 if (!mempool_make_dyn(
615 &iso->sprites, iso->max_num_sprites, sizeof(SpriteData))) {
616 return false;
617 }
618 }
619 if (mem_capacity(&iso->sheets) == 0) {
620 // Using a block size of 1 byte for sprite sheet data.
621 if (!mem_make_dyn(&iso->sheets, iso->sprite_sheet_pool_size_bytes, 1)) {
622 return false;
623 }
624 }
625
626 // Load sprite sheet file.
627 printf("Load sprite sheet: %s\n", filepath);
628 FILE* file = fopen(filepath, "rb");
629 if (file == NULL) {
630 goto cleanup;
631 }
632 const size_t sheet_size = get_file_size(file);
633 SpriteSheetData* ss_sheet = mem_alloc(&iso->sheets, sheet_size);
634 if (!ss_sheet) {
635 goto cleanup;
636 }
637 if (fread(ss_sheet, sheet_size, 1, file) != 1) {
638 goto cleanup;
639 }
640
641 *p_sheet = mem_get_chunk_handle(&iso->sheets, ss_sheet);
642 success = true;
643
644cleanup:
645 // Pools remain initialized since client may attempt to load other sprites.
646 if (file != NULL) {
647 fclose(file);
648 }
649 if (!success) {
650 if (ss_sheet) {
651 mem_free(&iso->sheets, &ss_sheet);
652 }
653 }
654 return success;
655}
656
657Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) {
658 assert(iso);
659
660 SpriteData* sprite = mempool_alloc(&iso->sprites);
661 assert(sprite);
662
663 sprite->sheet = sheet;
664
665 return mempool_get_block_index(&iso->sprites, sprite);
666}
667
668#define with_sprite(SPRITE, BODY) \
669 { \
670 SpriteData* data = mempool_get_block(&iso->sprites, sprite); \
671 assert(data); \
672 BODY; \
673 }
674
675void isogfx_set_sprite_position(IsoGfx* iso, Sprite sprite, int x, int y) {
676 assert(iso);
677 with_sprite(sprite, {
678 data->position.x = x;
679 data->position.y = y;
680 });
681}
682
683void isogfx_set_sprite_animation(IsoGfx* iso, Sprite sprite, int animation) {
684 assert(iso);
685 with_sprite(sprite, { data->animation = animation; });
686}
687
688void isogfx_update(IsoGfx* iso, double t) {
689 assert(iso);
690
691 // If this is the first time update() is called after initialization, just
692 // record the starting animation time.
693 if (iso->last_animation_time == 0.0) {
694 iso->last_animation_time = t;
695 return;
696 }
697
698 if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) {
699 // TODO: Consider linking animated sprites in a list so that we only walk
700 // over those here and not also the static sprites.
701 mempool_foreach(&iso->sprites, sprite, {
702 const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet);
703 assert(sheet); // TODO: Make this a hard assert inside the mem/pool.
704 const SpriteSheetRow* row =
705 get_sprite_sheet_row(sheet, sprite->animation);
706 sprite->frame = (sprite->frame + 1) % row->num_cols;
707 });
708
709 iso->last_animation_time = t;
710 }
711}
712
713// -----------------------------------------------------------------------------
714// Rendering and picking.
715// -----------------------------------------------------------------------------
716
717typedef struct CoordSystem {
718 ivec2 o; /// Origin.
719 ivec2 x;
720 ivec2 y;
721} CoordSystem;
722
723/// Create the basis for the isometric coordinate system with origin and vectors
724/// expressed in the Cartesian system.
725static CoordSystem make_iso_coord_system(const IsoGfx* iso) {
726 assert(iso);
727 const ivec2 o = {iso->screen_width / 2, 0};
728 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
729 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
730 return (CoordSystem){o, x, y};
731}
732
733/// Get the screen position of the top diamond-corner of the tile at world
734/// (x,y).
735static ivec2 GetTileScreenOrigin(
736 const CoordSystem iso_space, int world_x, int world_y) {
737 const ivec2 vx_offset = ivec2_scale(iso_space.x, world_x);
738 const ivec2 vy_offset = ivec2_scale(iso_space.y, world_y);
739 const ivec2 screen_origin =
740 ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset));
741
742 return screen_origin;
743}
744
745static Pixel alpha_blend(Pixel src, Pixel dst) {
746 if ((src.a == 255) || (dst.a == 0)) {
747 return src;
748 }
749 const uint16_t one_minus_alpha = 255 - src.a;
750#define blend(s, d) \
751 (Channel)( \
752 (double)((uint16_t)s * (uint16_t)src.a + \
753 (uint16_t)d * one_minus_alpha) / \
754 255.0)
755 return (Pixel){
756 .r = blend(src.r, dst.r),
757 .g = blend(src.g, dst.g),
758 .b = blend(src.b, dst.b),
759 .a = src.a};
760}
761
762/// Draw a rectangle (tile or sprite).
763///
764/// The rectangle's top-left corner is mapped to the screen space position given
765/// by 'top_left'.
766///
767/// The rectangle's pixels are assumed to be arranged in a linear, row-major
768/// fashion.
769///
770/// If indices are given, then the image is assumed to be colour-paletted, where
771/// 'pixels' is the palette and 'indices' the pixel indices. Otherwise, the
772/// image is assumed to be in plain RGBA format.
773static void draw_rect(
774 IsoGfx* iso, ivec2 top_left, int rect_width, int rect_height,
775 const Pixel* pixels, const uint8_t* indices) {
776 assert(iso);
777
778#define rect_pixel(X, Y) \
779 (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X])
780
781 // Rect origin can be outside screen bounds, so we must offset accordingly to
782 // draw only the visible portion.
783#define max(a, b) (a > b ? a : b)
784 const int px_offset = max(0, -top_left.x);
785 const int py_offset = max(0, -top_left.y);
786
787 // Rect can exceed screen bounds, so clip along Y and X as we draw.
788 for (int py = py_offset;
789 (py < rect_height) && (top_left.y + py < iso->screen_height); ++py) {
790 const int sy = top_left.y + py;
791 for (int px = px_offset;
792 (px < rect_width) && (top_left.x + px < iso->screen_width); ++px) {
793 const Pixel colour = rect_pixel(px, py);
794 if (colour.a > 0) {
795 const int sx = top_left.x + px;
796 const Pixel dst = screen_xy(iso, sx, sy);
797 const Pixel final = alpha_blend(colour, dst);
798 *screen_xy_mut(iso, sx, sy) = final;
799 }
800 }
801 }
802}
803
804/// Draw a tile.
805///
806/// 'screen_origin' is the screen coordinates of the top diamond-corner of the
807/// tile (the base tile for super tiles).
808/// World (0, 0) -> (screen_width / 2, 0).
809static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) {
810 assert(iso);
811
812 const TileData* tile_data = mempool_get_block(&iso->tiles, tile);
813 assert(tile_data);
814 const Pixel* pixels = tile_xy_const_ref(iso, tile_data, 0, 0);
815
816 // Move from the top diamond-corner to the top-left corner of the tile image.
817 // For regular tiles, tile height == base tile height, so the y offset is 0.
818 // For super tiles, move as high up as the height of the tile.
819 const ivec2 offset = {
820 -(iso->tile_width / 2), tile_data->height - iso->tile_height};
821 const ivec2 top_left = ivec2_add(screen_origin, offset);
822
823 draw_rect(iso, top_left, tile_data->width, tile_data->height, pixels, 0);
824}
825
826static void draw_world(IsoGfx* iso) {
827 assert(iso);
828
829 const int W = iso->screen_width;
830 const int H = iso->screen_height;
831
832 memset(iso->screen, 0, W * H * sizeof(Pixel));
833
834 const CoordSystem iso_space = make_iso_coord_system(iso);
835
836 // TODO: Culling.
837 // Ex: map the screen corners to tile space to cull.
838 // Ex: walk in screen space and fetch the tile.
839 // The tile-centric approach might be more cache-friendly since the
840 // screen-centric approach would juggle multiple tiles throughout the scan.
841 for (int wy = 0; wy < iso->world_height; ++wy) {
842 for (int wx = 0; wx < iso->world_width; ++wx) {
843 const Tile tile = world_xy(iso, wx, wy);
844 const ivec2 screen_origin = GetTileScreenOrigin(iso_space, wx, wy);
845 draw_tile(iso, screen_origin, tile);
846 }
847 }
848}
849
850static void draw_sprite(
851 IsoGfx* iso, ivec2 origin, const SpriteData* sprite,
852 const SpriteSheetData* sheet) {
853 assert(iso);
854 assert(sprite);
855 assert(sheet);
856 assert(sprite->animation >= 0);
857 assert(sprite->animation < sheet->num_rows);
858 assert(sprite->frame >= 0);
859
860 const SpriteSheetRow* row = get_sprite_sheet_row(sheet, sprite->animation);
861 const uint8_t* frame = get_sprite_sheet_sprite(sheet, row, sprite->frame);
862 draw_rect(
863 iso, origin, sheet->sprite_width, sheet->sprite_height,
864 sheet->palette.colours, frame);
865}
866
867static void draw_sprites(IsoGfx* iso) {
868 assert(iso);
869
870 const CoordSystem iso_space = make_iso_coord_system(iso);
871
872 mempool_foreach(&iso->sprites, sprite, {
873 const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet);
874 assert(sheet);
875
876 const ivec2 screen_origin =
877 GetTileScreenOrigin(iso_space, sprite->position.x, sprite->position.y);
878 draw_sprite(iso, screen_origin, sprite, sheet);
879 });
880}
881
882void isogfx_render(IsoGfx* iso) {
883 assert(iso);
884 draw_world(iso);
885 draw_sprites(iso);
886}
887
888void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) {
889 assert(iso);
890 assert(x >= 0);
891 assert(y >= 0);
892 assert(x < iso->world_width);
893 assert(y < iso->world_height);
894
895 const CoordSystem iso_space = make_iso_coord_system(iso);
896 const ivec2 screen_origin = GetTileScreenOrigin(iso_space, x, y);
897 draw_tile(iso, screen_origin, tile);
898}
899
900bool isogfx_resize(IsoGfx* iso, int screen_width, int screen_height) {
901 assert(iso);
902 assert(iso->screen);
903
904 const int current_size = iso->screen_width * iso->screen_height;
905 const int new_size = screen_width * screen_height;
906
907 if (new_size > current_size) {
908 Pixel* new_screen = calloc(new_size, sizeof(Pixel));
909 if (new_screen) {
910 free(iso->screen);
911 iso->screen = new_screen;
912 } else {
913 return false;
914 }
915 }
916 iso->screen_width = screen_width;
917 iso->screen_height = screen_height;
918 return true;
919}
920
921void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) {
922 assert(iso);
923 assert(width);
924 assert(height);
925 *width = iso->screen_width;
926 *height = iso->screen_height;
927}
928
929const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) {
930 assert(iso);
931 return iso->screen;
932}
933
934void isogfx_pick_tile(
935 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) {
936 assert(iso);
937 assert(xiso);
938 assert(yiso);
939
940 const vec2 xy_iso = cart2iso(
941 (vec2){.x = xcart, .y = ycart}, iso->tile_width, iso->tile_height,
942 iso->screen_width);
943
944 if ((0 <= xy_iso.x) && (xy_iso.x < iso->world_width) && (0 <= xy_iso.y) &&
945 (xy_iso.y < iso->world_height)) {
946 *xiso = (int)xy_iso.x;
947 *yiso = (int)xy_iso.y;
948 } else {
949 *xiso = -1;
950 *yiso = -1;
951 }
952}
diff --git a/gfx-iso/tools/mkasset.py b/gfx-iso/tools/mkasset.py
deleted file mode 100644
index 3ca8a1d..0000000
--- a/gfx-iso/tools/mkasset.py
+++ /dev/null
@@ -1,324 +0,0 @@
1# Converts assets to binary formats (.ts, .tm, .ss) for the engine.
2#
3# Input file formats:
4# - Tiled tile set (.tsx)
5# - Tiled tile map (.tmx)
6# - Sprite sheets (.jpg, .png, etc), 1 row per animation.
7#
8# Output file formats:
9# - Binary tile set file (.ts)
10# - Binary tile map file (.tm)
11# - Binary sprite sheet file (.ss)
12#
13import argparse
14import ctypes
15import os
16from PIL import Image
17import sys
18from xml.etree import ElementTree
19
20# Maximum length of path strings in .TS and .TM files.
21# Must match the engine's value.
22MAX_PATH_LENGTH = 128
23
24
25def drop_extension(filepath):
26 return filepath[:filepath.rfind('.')]
27
28
29def to_char_array(string, length):
30 """Convert a string to a fixed-length ASCII char array.
31
32 The length of str must be at most length-1 so that the resulting string can
33 be null-terminated.
34 """
35 assert (len(string) < length)
36 chars = string.encode("ascii")
37 nulls = ("\0" * (length - len(string))).encode("ascii")
38 return chars + nulls
39
40
41def convert_tsx(input_filepath, output_filepath):
42 """Converts a Tiled .tsx tileset file to a .TS tile set file."""
43 xml = ElementTree.parse(input_filepath)
44 root = xml.getroot()
45
46 tile_count = int(root.attrib["tilecount"])
47 max_tile_width = int(root.attrib["tilewidth"])
48 max_tile_height = int(root.attrib["tileheight"])
49
50 print(f"Tile count: {tile_count}")
51 print(f"Max width: {max_tile_width}")
52 print(f"Max height: {max_tile_height}")
53
54 with open(output_filepath, 'bw') as output:
55 output.write(ctypes.c_uint16(tile_count))
56 output.write(ctypes.c_uint16(max_tile_width))
57 output.write(ctypes.c_uint16(max_tile_height))
58
59 num_tile = 0
60 for tile in root:
61 # Skip the "grid" and other non-tile elements.
62 if not tile.tag == "tile":
63 continue
64
65 # Assuming tiles are numbered 0..N.
66 tile_id = int(tile.attrib["id"])
67 assert (tile_id == num_tile)
68 num_tile += 1
69
70 image = tile[0]
71 tile_width = int(image.attrib["width"])
72 tile_height = int(image.attrib["height"])
73 tile_path = image.attrib["source"]
74
75 output.write(ctypes.c_uint16(tile_width))
76 output.write(ctypes.c_uint16(tile_height))
77
78 with Image.open(tile_path) as im:
79 bytes = im.convert('RGBA').tobytes()
80 output.write(bytes)
81
82
83def convert_tmx(input_filepath, output_filepath):
84 """Converts a Tiled .tmx file to a .TM tile map file."""
85 xml = ElementTree.parse(input_filepath)
86 root = xml.getroot()
87
88 map_width = int(root.attrib["width"])
89 map_height = int(root.attrib["height"])
90 base_tile_width = int(root.attrib["tilewidth"])
91 base_tile_height = int(root.attrib["tileheight"])
92 num_layers = 1
93
94 print(f"Map width: {map_width}")
95 print(f"Map height: {map_height}")
96 print(f"Tile width: {base_tile_width}")
97 print(f"Tile height: {base_tile_height}")
98
99 with open(output_filepath, 'bw') as output:
100 output.write(ctypes.c_uint16(map_width))
101 output.write(ctypes.c_uint16(map_height))
102 output.write(ctypes.c_uint16(base_tile_width))
103 output.write(ctypes.c_uint16(base_tile_height))
104 output.write(ctypes.c_uint16(num_layers))
105
106 tileset_path = None
107
108 for child in root:
109 if child.tag == "tileset":
110 tileset = child
111 tileset_path = tileset.attrib["source"]
112
113 print(f"Tile set: {tileset_path}")
114
115 tileset_path = tileset_path.replace("tsx", "ts")
116 elif child.tag == "layer":
117 layer = child
118 layer_id = int(layer.attrib["id"])
119 layer_width = int(layer.attrib["width"])
120 layer_height = int(layer.attrib["height"])
121
122 print(f"Layer: {layer_id}")
123 print(f"Width: {layer_width}")
124 print(f"Height: {layer_height}")
125
126 assert (tileset_path)
127 output.write(to_char_array(tileset_path, MAX_PATH_LENGTH))
128
129 # Assume the layer's dimensions matches the map's.
130 assert (layer_width == map_width)
131 assert (layer_height == map_height)
132
133 data = layer[0]
134 # Handle other encodings later.
135 assert (data.attrib["encoding"] == "csv")
136
137 csv = data.text.strip()
138 rows = csv.split('\n')
139 for row in rows:
140 tile_ids = [x.strip() for x in row.split(',') if x]
141 for tile_id in tile_ids:
142 output.write(ctypes.c_uint16(int(tile_id)))
143
144
145def get_num_cols(image, sprite_width):
146 """Return the number of non-empty columns in the image.
147
148 Assumes no gaps in the columns.
149 """
150 assert (image.width % sprite_width == 0)
151 num_cols = image.width // sprite_width
152
153 # Start the search from right to left.
154 for col in reversed(range(1, num_cols)):
155 left = (col - 1) * sprite_width
156 right = col * sprite_width
157 rect = image.crop((left, 0, right, image.height))
158 min_max = rect.getextrema()
159 for (channel_min, channel_max) in min_max:
160 if channel_min != 0 or channel_max != 0:
161 # 'col' is the rightmost non-empty column.
162 # Assuming no gaps, col+1 is the number of non-empty columns.
163 return col + 1
164
165 return 0
166
167
168def get_sprite_sheet_rows(im, sprite_width, sprite_height):
169 """Gets the individual rows of a sprite sheet.
170
171 The input sprite sheet can have any number of rows.
172
173 Returns a list of lists [[sprite]], one inner list for the columns in each
174 row.
175 """
176 # Sprite sheet's width and height must be integer multiples of the
177 # sprite's width and height.
178 assert (im.width % sprite_width == 0)
179 assert (im.height % sprite_height == 0)
180
181 num_rows = im.height // sprite_height
182
183 rows = []
184 for row in range(num_rows):
185 # Get the number of columns.
186 upper = row * sprite_height
187 lower = (row + 1) * sprite_height
188 whole_row = im.crop((0, upper, im.width, lower))
189 num_cols = get_num_cols(whole_row, sprite_width)
190 assert (num_cols > 0)
191
192 # Crop the row into N columns.
193 cols = []
194 for i in range(num_cols):
195 left = i * sprite_width
196 right = (i + 1) * sprite_width
197 sprite = im.crop((left, upper, right, lower))
198 cols.append(sprite)
199
200 assert (len(cols) == num_cols)
201 rows.append(cols)
202
203 return rows
204
205
206def make_image_from_rows(rows, sprite_width, sprite_height):
207 """Concatenate the rows into a single RGBA image."""
208 im_width = sprite_width * max(len(row) for row in rows)
209 im_height = len(rows) * sprite_height
210 im = Image.new('RGBA', (im_width, im_height))
211 y = 0
212 for row in rows:
213 x = 0
214 for sprite in row:
215 im.paste(sprite.convert('RGBA'), (x, y))
216 x += sprite_width
217 y += sprite_height
218 return im
219
220
221def convert_sprite_sheet(input_file_paths, sprite_width, sprite_height,
222 output_filepath):
223 """Converts a set of sprite sheet images into a binary sprite sheet file
224 (.ss).
225
226 The input sprite sheets can have any number of rows, one row per animation.
227 All rows from all sprite sheets are concatenated in the output file.
228
229 The sprite's width and height is assumed constant throughout the input
230 sprite sheets.
231 """
232 rows = []
233 for input_filepath in input_file_paths:
234 with Image.open(input_filepath) as sprite_sheet:
235 rows.extend(
236 get_sprite_sheet_rows(sprite_sheet, sprite_width,
237 sprite_height))
238
239 im = make_image_from_rows(rows, sprite_width, sprite_height)
240 im = im.convert(mode="P", palette=Image.ADAPTIVE, colors=256)
241
242 # The sprite data in 'rows' is no longer needed.
243 # Keep just the number of columns per row.
244 rows = [len(row) for row in rows]
245
246 with open(output_filepath, 'bw') as output:
247 output.write(ctypes.c_uint16(sprite_width))
248 output.write(ctypes.c_uint16(sprite_height))
249 output.write(ctypes.c_uint16(len(rows)))
250
251 # Write palette.
252 # getpalette() returns 256 colors, but the palette might use less than
253 # that. getcolors() returns the number of unique colors.
254 # getpalette() also returns a flattened list, which is why we must *4.
255 num_colours = len(im.getcolors())
256 colours = im.getpalette(rawmode="RGBA")[:4 * num_colours]
257 palette = []
258 for i in range(0, 4 * num_colours, 4):
259 palette.append((colours[i], colours[i + 1], colours[i + 2],
260 colours[i + 3]))
261
262 output.write(ctypes.c_uint16(len(palette)))
263 output.write(bytearray(colours))
264
265 print(f"Sprite width: {sprite_width}")
266 print(f"Sprite height: {sprite_height}")
267 print(f"Rows: {len(rows)}")
268 print(f"Colours: {len(palette)}")
269
270 # print("Palette")
271 # for i, colour in enumerate(palette):
272 # print(f"{i}: {colour}")
273
274 for row, num_columns in enumerate(rows):
275 output.write(ctypes.c_uint16(num_columns))
276 upper = row * sprite_height
277 lower = (row + 1) * sprite_height
278 for col in range(num_columns):
279 left = col * sprite_width
280 right = (col + 1) * sprite_width
281 sprite = im.crop((left, upper, right, lower))
282 sprite_bytes = sprite.tobytes()
283
284 assert (len(sprite_bytes) == sprite_width * sprite_height)
285 output.write(sprite_bytes)
286
287 # if (row == 0) and (col == 0):
288 # print(f"Sprite: ({len(sprite_bytes)})")
289 # print(list(sprite_bytes))
290 # sprite.save("out.png")
291
292
293def main():
294 parser = argparse.ArgumentParser()
295 parser.add_argument("input",
296 nargs="+",
297 help="Input file (.tsx, .tmx) or path regex (sprite sheets)")
298 parser.add_argument("--width", type=int, help="Sprite width in pixels")
299 parser.add_argument("--height", type=int, help="Sprite height in pixels")
300 parser.add_argument("--out", help="Output file (sprite sheets)")
301 args = parser.parse_args()
302
303 if ".tsx" in args.input:
304 output_filepath_no_ext = drop_extension(args.input)
305 output_filepath = output_filepath_no_ext + ".ts"
306 convert_tsx(args.input, output_filepath)
307 elif ".tmx" in args.input:
308 output_filepath_no_ext = drop_extension(args.input)
309 output_filepath = output_filepath_no_ext + ".tm"
310 convert_tmx(args.input, output_filepath)
311 else:
312 # Sprite sheets.
313 if not args.width or not args.height:
314 print("Sprite width and height must be given")
315 return 1
316 output_filepath = args.out if args.out else "out.ss"
317 convert_sprite_sheet(args.input, args.width, args.height,
318 output_filepath)
319
320 return 0
321
322
323if __name__ == '__main__':
324 sys.exit(main())