diff options
Diffstat (limited to 'game')
-rw-r--r-- | game/CMakeLists.txt | 22 | ||||
-rw-r--r-- | game/src/game.c | 223 | ||||
-rw-r--r-- | game/src/game.h | 21 | ||||
-rw-r--r-- | game/src/plugins/CMakeLists.txt | 29 | ||||
-rw-r--r-- | game/src/plugins/plugin.h | 52 | ||||
-rw-r--r-- | game/src/plugins/pong.c | 237 | ||||
-rw-r--r-- | game/src/plugins/texture_view.c | 144 | ||||
-rw-r--r-- | game/src/plugins/viewer.c | 366 |
8 files changed, 0 insertions, 1094 deletions
diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt deleted file mode 100644 index 3a88bb7..0000000 --- a/game/CMakeLists.txt +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | cmake_minimum_required(VERSION 3.0) | ||
2 | |||
3 | add_subdirectory(src/plugins) | ||
4 | |||
5 | project(game) | ||
6 | |||
7 | add_executable(game | ||
8 | src/game.c) | ||
9 | |||
10 | target_include_directories(game PRIVATE | ||
11 | src/) | ||
12 | |||
13 | target_link_libraries(game PRIVATE | ||
14 | cstring | ||
15 | error | ||
16 | gfx | ||
17 | gfx-app | ||
18 | list | ||
19 | log | ||
20 | math | ||
21 | mempool | ||
22 | plugin) | ||
diff --git a/game/src/game.c b/game/src/game.c deleted file mode 100644 index dc2248b..0000000 --- a/game/src/game.c +++ /dev/null | |||
@@ -1,223 +0,0 @@ | |||
1 | /* | ||
2 | * Main game module with entry point and game loop. | ||
3 | * | ||
4 | * The game module sets up the window and GL context and defers the core game | ||
5 | * logic to a plugin. | ||
6 | */ | ||
7 | #define _GNU_SOURCE 200112L // For readlink() | ||
8 | |||
9 | #include "game.h" | ||
10 | |||
11 | #include "plugins/plugin.h" | ||
12 | |||
13 | #include <gfx/app.h> | ||
14 | #include <gfx/core.h> | ||
15 | #include <gfx/gfx.h> | ||
16 | #include <gfx/renderer.h> | ||
17 | #include <gfx/scene/camera.h> | ||
18 | #include <gfx/scene/node.h> | ||
19 | #include <gfx/scene/object.h> | ||
20 | #include <gfx/scene/scene.h> | ||
21 | |||
22 | #include <error.h> | ||
23 | #include <log/log.h> | ||
24 | #include <math/camera.h> | ||
25 | #include <plugin.h> | ||
26 | |||
27 | #include <assert.h> | ||
28 | #include <stdbool.h> | ||
29 | #include <stdio.h> | ||
30 | #include <stdlib.h> | ||
31 | |||
32 | #include <linux/limits.h> | ||
33 | |||
34 | #include <unistd.h> | ||
35 | |||
36 | #undef _GNU_SOURCE | ||
37 | |||
38 | static const int WIDTH = 1350; | ||
39 | static const int HEIGHT = 900; | ||
40 | static const int MAX_FPS = 60; | ||
41 | |||
42 | typedef struct GfxAppState { | ||
43 | Game game; | ||
44 | } GfxAppState; | ||
45 | |||
46 | /// Initialize the game's plugin. | ||
47 | static bool init_plugin(Game* game) { | ||
48 | assert(game); | ||
49 | assert(game->plugin); | ||
50 | // Plugin state is allowed to be null, either when the plugin does not | ||
51 | // expose an init() or when init() does not initialize a state. | ||
52 | if (plugin_resolve(game->plugin, plugin_init, "init")) { | ||
53 | State* plugin_state = 0; | ||
54 | if (!plugin_call(game->plugin, plugin_init, "init", game, &plugin_state)) { | ||
55 | return false; | ||
56 | } | ||
57 | set_plugin_state(game->plugin, plugin_state); | ||
58 | } | ||
59 | return true; // Plugin does not need to expose an init(). | ||
60 | } | ||
61 | |||
62 | /// Shutdown the game's plugin. | ||
63 | /// The game's plugin is allowed to be null in the call to this function. | ||
64 | static void shutdown_plugin(Game* game) { | ||
65 | assert(game); | ||
66 | if (game->plugin && | ||
67 | (plugin_resolve(game->plugin, plugin_shutdown, "shutdown"))) { | ||
68 | void* plugin_state = get_plugin_state(game->plugin); | ||
69 | plugin_call(game->plugin, plugin_shutdown, "shutdown", game, plugin_state); | ||
70 | set_plugin_state(game->plugin, 0); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | /// Boot the game's plugin. | ||
75 | static bool boot_plugin(Game* game) { | ||
76 | assert(game); | ||
77 | assert(game->plugin); | ||
78 | if (plugin_resolve(game->plugin, plugin_boot, "boot")) { | ||
79 | void* plugin_state = get_plugin_state(game->plugin); | ||
80 | return plugin_call(game->plugin, plugin_boot, "boot", game, plugin_state); | ||
81 | } | ||
82 | return true; // Plugin does not need to expose a boot(). | ||
83 | } | ||
84 | |||
85 | /// Update the plugin's state. | ||
86 | static void update_plugin(Game* game, double t, double dt) { | ||
87 | assert(game); | ||
88 | assert(game->plugin); | ||
89 | if (plugin_resolve(game->plugin, plugin_update, "update")) { | ||
90 | void* plugin_state = get_plugin_state(game->plugin); | ||
91 | plugin_call( | ||
92 | game->plugin, plugin_update, "update", game, plugin_state, t, dt); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | /// Plugin render. | ||
97 | static void render_plugin(const Game* game) { | ||
98 | assert(game); | ||
99 | assert(game->plugin); | ||
100 | if (plugin_resolve(game->plugin, plugin_render, "render")) { | ||
101 | void* plugin_state = get_plugin_state(game->plugin); | ||
102 | plugin_call(game->plugin, plugin_render, "render", game, plugin_state); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | /// Plugin resize. | ||
107 | static void resize_plugin(Game* game, int width, int height) { | ||
108 | assert(game); | ||
109 | assert(game->plugin); | ||
110 | if (plugin_resolve(game->plugin, plugin_resize, "resize")) { | ||
111 | void* plugin_state = get_plugin_state(game->plugin); | ||
112 | plugin_call( | ||
113 | game->plugin, plugin_resize, "resize", game, plugin_state, width, | ||
114 | height); | ||
115 | } | ||
116 | } | ||
117 | |||
118 | static void Shutdown(Game* game); | ||
119 | |||
120 | static bool Init(Game* game, int argc, const char** argv) { | ||
121 | assert(game); | ||
122 | |||
123 | if (argc <= 1) { | ||
124 | LOGE("Usage: %s <plugin> [plugin args]", argv[0]); | ||
125 | return false; | ||
126 | } | ||
127 | |||
128 | // Syntax: game <plugin> [plugin args] | ||
129 | // | ||
130 | // Here we consume the <plugin> arg so that plugins receive the remainder | ||
131 | // args starting from 0. | ||
132 | game->argc = argc - 1; | ||
133 | game->argv = argv + 1; | ||
134 | |||
135 | char exe_path_buf[NAME_MAX] = {0}; | ||
136 | if (readlink("/proc/self/exe", exe_path_buf, sizeof(exe_path_buf)) == -1) { | ||
137 | LOGE("readlink(/proc/self/exe) failed"); | ||
138 | goto cleanup; | ||
139 | } | ||
140 | |||
141 | // Replace the last / with a null terminator to remove the exe file from the | ||
142 | // path. This gets the file's parent directory. | ||
143 | *strrchr(exe_path_buf, '/') = 0; | ||
144 | |||
145 | const mstring exe_dir = mstring_make(exe_path_buf); | ||
146 | const mstring plugins_path = mstring_concat_cstr(exe_dir, "/src/plugins"); | ||
147 | |||
148 | if (!(game->plugin_engine = new_plugin_engine( | ||
149 | &(PluginEngineDesc){.plugins_dir = mstring_cstr(&plugins_path)}))) { | ||
150 | goto cleanup; | ||
151 | } | ||
152 | |||
153 | const char* plugin = argv[1]; | ||
154 | if (!(game->plugin = load_plugin(game->plugin_engine, plugin))) { | ||
155 | goto cleanup; | ||
156 | } | ||
157 | |||
158 | if (!(game->gfx = gfx_init())) { | ||
159 | goto cleanup; | ||
160 | } | ||
161 | |||
162 | if (!init_plugin(game)) { | ||
163 | goto cleanup; | ||
164 | } | ||
165 | if (!boot_plugin(game)) { | ||
166 | goto cleanup; | ||
167 | } | ||
168 | |||
169 | return true; | ||
170 | |||
171 | cleanup: | ||
172 | LOGE("Gfx error: %s", get_error()); | ||
173 | Shutdown(game); | ||
174 | return false; | ||
175 | } | ||
176 | |||
177 | static void Shutdown(Game* game) { | ||
178 | assert(game); | ||
179 | shutdown_plugin(game); | ||
180 | if (game->gfx) { | ||
181 | gfx_destroy(&game->gfx); | ||
182 | } | ||
183 | if (game->plugin) { | ||
184 | delete_plugin(&game->plugin); | ||
185 | } | ||
186 | if (game->plugin_engine) { | ||
187 | delete_plugin_engine(&game->plugin_engine); | ||
188 | } | ||
189 | } | ||
190 | |||
191 | static void Update(Game* game, double t, double dt) { | ||
192 | plugin_engine_update(game->plugin_engine); | ||
193 | if (plugin_reloaded(game->plugin)) { | ||
194 | shutdown_plugin(game); | ||
195 | const bool result = init_plugin(game); | ||
196 | assert(result); // TODO: handle error better. | ||
197 | |||
198 | // Trigger a resize just like the initial resize that occurs when the gfx | ||
199 | // application starts. | ||
200 | resize_plugin(game, game->width, game->height); | ||
201 | } | ||
202 | |||
203 | update_plugin(game, t, dt); | ||
204 | } | ||
205 | |||
206 | static void Render(const Game* game) { | ||
207 | GfxCore* gfxcore = gfx_get_core(game->gfx); | ||
208 | gfx_start_frame(gfxcore); | ||
209 | render_plugin(game); | ||
210 | gfx_end_frame(gfxcore); | ||
211 | } | ||
212 | |||
213 | static void Resize(Game* game, int width, int height) { | ||
214 | game->width = width; | ||
215 | game->height = height; | ||
216 | |||
217 | GfxCore* gfxcore = gfx_get_core(game->gfx); | ||
218 | gfx_set_viewport(gfxcore, 0, 0, width, height); | ||
219 | |||
220 | resize_plugin(game, width, height); | ||
221 | } | ||
222 | |||
223 | GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS, "Game"); | ||
diff --git a/game/src/game.h b/game/src/game.h deleted file mode 100644 index 579ba3c..0000000 --- a/game/src/game.h +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | /* | ||
2 | * Header file defining the game state, included by plugins. | ||
3 | */ | ||
4 | #pragma once | ||
5 | |||
6 | typedef struct PluginEngine PluginEngine; | ||
7 | typedef struct Plugin Plugin; | ||
8 | typedef struct Gfx Gfx; | ||
9 | typedef struct Scene Scene; | ||
10 | typedef struct SceneCamera SceneCamera; | ||
11 | |||
12 | /// Game state. | ||
13 | typedef struct { | ||
14 | int argc; | ||
15 | const char** argv; | ||
16 | PluginEngine* plugin_engine; | ||
17 | Plugin* plugin; | ||
18 | Gfx* gfx; | ||
19 | int width; | ||
20 | int height; | ||
21 | } Game; | ||
diff --git a/game/src/plugins/CMakeLists.txt b/game/src/plugins/CMakeLists.txt deleted file mode 100644 index 8661598..0000000 --- a/game/src/plugins/CMakeLists.txt +++ /dev/null | |||
@@ -1,29 +0,0 @@ | |||
1 | cmake_minimum_required(VERSION 3.0) | ||
2 | |||
3 | project(plugins) | ||
4 | |||
5 | set(LINK_LIBRARIES cstring math gfx gfx-app) | ||
6 | |||
7 | # Viewer | ||
8 | |||
9 | add_library(viewer SHARED | ||
10 | viewer.c) | ||
11 | |||
12 | target_link_libraries(viewer PUBLIC | ||
13 | ${LINK_LIBRARIES}) | ||
14 | |||
15 | # Texture viewer | ||
16 | |||
17 | add_library(texture_view SHARED | ||
18 | texture_view.c) | ||
19 | |||
20 | target_link_libraries(texture_view PUBLIC | ||
21 | ${LINK_LIBRARIES}) | ||
22 | |||
23 | # Pong | ||
24 | |||
25 | add_library(pong SHARED | ||
26 | pong.c) | ||
27 | |||
28 | target_link_libraries(pong PUBLIC | ||
29 | ${LINK_LIBRARIES}) | ||
diff --git a/game/src/plugins/plugin.h b/game/src/plugins/plugin.h deleted file mode 100644 index f7219c6..0000000 --- a/game/src/plugins/plugin.h +++ /dev/null | |||
@@ -1,52 +0,0 @@ | |||
1 | /* | ||
2 | * Game plugin. | ||
3 | */ | ||
4 | #pragma once | ||
5 | |||
6 | #include "../game.h" | ||
7 | |||
8 | #include <gfx/gfx.h> | ||
9 | #include <gfx/scene.h> | ||
10 | |||
11 | #include <stdbool.h> | ||
12 | |||
13 | typedef struct State State; | ||
14 | |||
15 | /// Initialize the plugin, which may optionally return a state object. | ||
16 | /// | ||
17 | /// This function is called every time the plugin is (re)loaded. | ||
18 | /// | ||
19 | /// It is assumed that the plugin's state is fully encapsulated in the returned | ||
20 | /// state object. The plugin should not store any (mutable) state outside of the | ||
21 | /// returned state object (e.g., no mutable global variables.) | ||
22 | bool init(Game*, State**); | ||
23 | |||
24 | /// Shut down the plugin. | ||
25 | /// | ||
26 | /// This function is called before the plugin is unloaded. | ||
27 | /// | ||
28 | /// The plugin should perform any destruction needed, but not free the state | ||
29 | /// object; freeing the state object's memory is handled by the caller. | ||
30 | void shutdown(Game*, State*); | ||
31 | |||
32 | /// Function called the first time the plugin is loaded throughout the | ||
33 | /// application's lifetime. This allows the plugin to do one-time initialization | ||
34 | /// of the game state. | ||
35 | bool boot(Game*, State*); | ||
36 | |||
37 | /// Update the plugin's and the game's state. | ||
38 | void update(Game*, State*, double t, double dt); | ||
39 | |||
40 | /// Render hook. | ||
41 | void render(const Game*, const State*); | ||
42 | |||
43 | /// Called when the game's window is resized. | ||
44 | void resize(Game*, State*, int width, int height); | ||
45 | |||
46 | // Signatures for the plugin's exposed functions. | ||
47 | typedef bool (*plugin_init)(Game*, State**); | ||
48 | typedef bool (*plugin_shutdown)(Game*, State*); | ||
49 | typedef bool (*plugin_boot)(Game*, State*); | ||
50 | typedef void (*plugin_update)(Game*, State*, double t, double dt); | ||
51 | typedef void (*plugin_render)(const Game*, const State*); | ||
52 | typedef void (*plugin_resize)(Game* game, State* state, int width, int height); | ||
diff --git a/game/src/plugins/pong.c b/game/src/plugins/pong.c deleted file mode 100644 index c1c55be..0000000 --- a/game/src/plugins/pong.c +++ /dev/null | |||
@@ -1,237 +0,0 @@ | |||
1 | #include "plugin.h" | ||
2 | |||
3 | #include <gfx/app.h> | ||
4 | #include <gfx/gfx.h> | ||
5 | #include <gfx/renderer.h> | ||
6 | |||
7 | #include <math/mat4.h> | ||
8 | #include <math/vec2.h> | ||
9 | #include <math/vec4.h> | ||
10 | |||
11 | #include <stdlib.h> | ||
12 | |||
13 | static const vec2 PAD_SIZE = (vec2){120, 20}; | ||
14 | static const R PLAYER_Y_OFFSET = 50; | ||
15 | static const R PLAYER_SPEED = 800; | ||
16 | |||
17 | static const R ENEMY_SPEED = 2; | ||
18 | |||
19 | static const R BALL_SIZE = 18; | ||
20 | static const R BALL_SPEED = 360; // In each dimension. | ||
21 | |||
22 | static const R EPS = (R)1e-3; | ||
23 | |||
24 | typedef struct Player { | ||
25 | vec2 position; | ||
26 | } Player; | ||
27 | |||
28 | typedef struct Ball { | ||
29 | vec2 position; | ||
30 | vec2 velocity; | ||
31 | } Ball; | ||
32 | |||
33 | typedef struct State { | ||
34 | bool game_started; | ||
35 | Player human; | ||
36 | Player enemy; | ||
37 | Ball ball; | ||
38 | mat4 viewProjection; | ||
39 | } State; | ||
40 | |||
41 | bool init(Game* game, State** pp_state) { | ||
42 | assert(game); | ||
43 | |||
44 | State* state = calloc(1, sizeof(State)); | ||
45 | if (!state) { | ||
46 | return false; | ||
47 | } | ||
48 | |||
49 | *pp_state = state; | ||
50 | return true; | ||
51 | |||
52 | cleanup: | ||
53 | free(state); | ||
54 | return false; | ||
55 | } | ||
56 | |||
57 | void shutdown(Game* game, State* state) { | ||
58 | assert(game); | ||
59 | assert(state); | ||
60 | } | ||
61 | |||
62 | static void move_ball(Ball* ball, R dt, int width, int height) { | ||
63 | assert(ball); | ||
64 | |||
65 | const R offset = BALL_SIZE / 2; | ||
66 | |||
67 | ball->position = vec2_add(ball->position, vec2_scale(ball->velocity, dt)); | ||
68 | |||
69 | // Right wall. | ||
70 | if (ball->position.x + offset > (R)width) { | ||
71 | ball->position.x = (R)width - offset - EPS; | ||
72 | ball->velocity.x = -ball->velocity.x; | ||
73 | } | ||
74 | // Left wall. | ||
75 | else if (ball->position.x - offset < 0) { | ||
76 | ball->position.x = offset + EPS; | ||
77 | ball->velocity.x = -ball->velocity.x; | ||
78 | } | ||
79 | // Top wall. | ||
80 | if (ball->position.y + offset > (R)height) { | ||
81 | ball->position.y = (R)height - offset - EPS; | ||
82 | ball->velocity.y = -ball->velocity.y; | ||
83 | } | ||
84 | // Bottom wall. | ||
85 | else if (ball->position.y - offset < 0) { | ||
86 | ball->position.y = offset + EPS; | ||
87 | ball->velocity.y = -ball->velocity.y; | ||
88 | } | ||
89 | } | ||
90 | |||
91 | void move_enemy_player(int width, Player* player, R t) { | ||
92 | const R half_width = (R)width / 2; | ||
93 | const R amplitude = half_width - (PAD_SIZE.x / 2); | ||
94 | player->position.x = half_width + amplitude * sinf(t * ENEMY_SPEED); | ||
95 | } | ||
96 | |||
97 | void move_human_player(Player* player, R dt) { | ||
98 | assert(player); | ||
99 | |||
100 | R speed = 0; | ||
101 | if (gfx_app_is_key_pressed('a')) { | ||
102 | speed -= PLAYER_SPEED; | ||
103 | } | ||
104 | if (gfx_app_is_key_pressed('d')) { | ||
105 | speed += PLAYER_SPEED; | ||
106 | } | ||
107 | |||
108 | player->position.x += speed * dt; | ||
109 | } | ||
110 | |||
111 | void clamp_player(Player* player, int width) { | ||
112 | assert(player); | ||
113 | |||
114 | const R offset = PAD_SIZE.x / 2; | ||
115 | |||
116 | // Left wall. | ||
117 | if (player->position.x + offset > (R)width) { | ||
118 | player->position.x = (R)width - offset; | ||
119 | } | ||
120 | // Right wall. | ||
121 | else if (player->position.x - offset < 0) { | ||
122 | player->position.x = offset; | ||
123 | } | ||
124 | } | ||
125 | |||
126 | void collide_ball(vec2 old_ball_position, const Player* player, Ball* ball) { | ||
127 | assert(player); | ||
128 | assert(ball); | ||
129 | |||
130 | // Discrete but simple collision. Checks for intersection and moves the ball | ||
131 | // back by a small epsilon. | ||
132 | |||
133 | // Player bounding box. | ||
134 | const vec2 player_pmin = vec2_make( | ||
135 | player->position.x - PAD_SIZE.x / 2, player->position.y - PAD_SIZE.y / 2); | ||
136 | const vec2 player_pmax = vec2_make( | ||
137 | player->position.x + PAD_SIZE.x / 2, player->position.y + PAD_SIZE.y / 2); | ||
138 | |||
139 | // Ball bounding box. | ||
140 | const vec2 ball_pmin = vec2_make( | ||
141 | ball->position.x - BALL_SIZE / 2, ball->position.y - BALL_SIZE / 2); | ||
142 | const vec2 ball_pmax = vec2_make( | ||
143 | ball->position.x + BALL_SIZE / 2, ball->position.y + BALL_SIZE / 2); | ||
144 | |||
145 | // Check for intersection and update ball. | ||
146 | if (!((ball_pmax.x < player_pmin.x) || (ball_pmin.x > player_pmax.x) || | ||
147 | (ball_pmax.y < player_pmin.y) || (ball_pmin.y > player_pmax.y))) { | ||
148 | ball->position = | ||
149 | vec2_add(old_ball_position, vec2_scale(ball->velocity, -EPS)); | ||
150 | ball->velocity.y = -ball->velocity.y; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | void update(Game* game, State* state, double t, double dt) { | ||
155 | assert(game); | ||
156 | assert(state); | ||
157 | |||
158 | // TODO: Move game width/height to GfxApp query functions? | ||
159 | const vec2 old_ball_position = state->ball.position; | ||
160 | move_ball(&state->ball, (R)dt, game->width, game->height); | ||
161 | move_human_player(&state->human, (R)dt); | ||
162 | move_enemy_player(game->width, &state->enemy, (R)t); | ||
163 | clamp_player(&state->human, game->width); | ||
164 | collide_ball(old_ball_position, &state->human, &state->ball); | ||
165 | collide_ball(old_ball_position, &state->enemy, &state->ball); | ||
166 | } | ||
167 | |||
168 | static void draw_player(ImmRenderer* imm, const Player* player) { | ||
169 | assert(imm); | ||
170 | assert(player); | ||
171 | |||
172 | const vec2 half_box = vec2_div(PAD_SIZE, vec2_make(2, 2)); | ||
173 | |||
174 | const vec2 pmin = vec2_sub(player->position, half_box); | ||
175 | const vec2 pmax = vec2_add(player->position, half_box); | ||
176 | const aabb2 box = aabb2_make(pmin, pmax); | ||
177 | |||
178 | gfx_imm_draw_aabb2(imm, box); | ||
179 | } | ||
180 | |||
181 | static void draw_ball(ImmRenderer* imm, const Ball* ball) { | ||
182 | assert(imm); | ||
183 | assert(ball); | ||
184 | |||
185 | const vec2 half_box = vec2_make(BALL_SIZE / 2, BALL_SIZE / 2); | ||
186 | const vec2 pmin = vec2_sub(ball->position, half_box); | ||
187 | const vec2 pmax = vec2_add(ball->position, half_box); | ||
188 | const aabb2 box = aabb2_make(pmin, pmax); | ||
189 | |||
190 | gfx_imm_draw_aabb2(imm, box); | ||
191 | } | ||
192 | |||
193 | void render(const Game* game, const State* state) { | ||
194 | assert(game); | ||
195 | assert(state); | ||
196 | |||
197 | ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); | ||
198 | gfx_imm_start(imm); | ||
199 | gfx_imm_set_view_projection_matrix(imm, &state->viewProjection); | ||
200 | gfx_imm_load_identity(imm); | ||
201 | gfx_imm_set_colour(imm, vec4_make(1, 1, 1, 1)); | ||
202 | draw_player(imm, &state->human); | ||
203 | draw_player(imm, &state->enemy); | ||
204 | draw_ball(imm, &state->ball); | ||
205 | gfx_imm_end(imm); | ||
206 | } | ||
207 | |||
208 | static R clamp_to_width(int width, R x, R extent) { | ||
209 | return min(x, (R)width - extent); | ||
210 | } | ||
211 | |||
212 | void resize(Game* game, State* state, int width, int height) { | ||
213 | assert(game); | ||
214 | assert(state); | ||
215 | |||
216 | state->viewProjection = mat4_ortho(0, (R)width, 0, (R)height, -1, 1); | ||
217 | |||
218 | state->human.position.y = PLAYER_Y_OFFSET; | ||
219 | state->enemy.position.y = (R)height - PLAYER_Y_OFFSET; | ||
220 | |||
221 | if (!state->game_started) { | ||
222 | state->human.position.x = (R)width / 2; | ||
223 | state->enemy.position.x = (R)width / 2; | ||
224 | |||
225 | state->ball.position = | ||
226 | vec2_div(vec2_make((R)width, (R)height), vec2_make(2, 2)); | ||
227 | |||
228 | state->ball.velocity = vec2_make(BALL_SPEED, BALL_SPEED); | ||
229 | |||
230 | state->game_started = true; | ||
231 | } else { | ||
232 | state->human.position.x = | ||
233 | clamp_to_width(width, state->human.position.x, PAD_SIZE.x / 2); | ||
234 | state->enemy.position.x = | ||
235 | clamp_to_width(width, state->enemy.position.x, PAD_SIZE.x / 2); | ||
236 | } | ||
237 | } | ||
diff --git a/game/src/plugins/texture_view.c b/game/src/plugins/texture_view.c deleted file mode 100644 index a8b2a94..0000000 --- a/game/src/plugins/texture_view.c +++ /dev/null | |||
@@ -1,144 +0,0 @@ | |||
1 | #include "plugin.h" | ||
2 | |||
3 | #include <gfx/asset.h> | ||
4 | #include <gfx/core.h> | ||
5 | #include <gfx/renderer.h> | ||
6 | #include <gfx/scene.h> | ||
7 | #include <gfx/util/geometry.h> | ||
8 | #include <gfx/util/shader.h> | ||
9 | |||
10 | #include <math/camera.h> | ||
11 | |||
12 | #include <assert.h> | ||
13 | #include <stdlib.h> | ||
14 | |||
15 | // Default texture to load if no texture is provided. | ||
16 | static const char* DEFAULT_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; | ||
17 | // static const char* DEFAULT_TEXTURE = "/assets/checkerboard.jpg"; | ||
18 | |||
19 | struct State { | ||
20 | Scene* scene; | ||
21 | SceneCamera* camera; | ||
22 | }; | ||
23 | |||
24 | bool init(Game* game, State** pp_state) { | ||
25 | assert(game); | ||
26 | assert(pp_state); | ||
27 | |||
28 | State* state = calloc(1, sizeof(State)); | ||
29 | if (!state) { | ||
30 | goto cleanup; | ||
31 | } | ||
32 | |||
33 | // Usage: [texture file] | ||
34 | const char* texture_file = game->argc > 1 ? game->argv[1] : DEFAULT_TEXTURE; | ||
35 | |||
36 | GfxCore* gfxcore = gfx_get_core(game->gfx); | ||
37 | |||
38 | const Texture* texture = gfx_load_texture( | ||
39 | game->gfx, &(LoadTextureCmd){ | ||
40 | .origin = AssetFromFile, | ||
41 | .type = LoadTexture, | ||
42 | .filtering = LinearFiltering, | ||
43 | .mipmaps = false, | ||
44 | .data.texture.filepath = mstring_make(texture_file)}); | ||
45 | if (!texture) { | ||
46 | goto cleanup; | ||
47 | } | ||
48 | |||
49 | ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore); | ||
50 | if (!shader) { | ||
51 | goto cleanup; | ||
52 | } | ||
53 | |||
54 | Geometry* geometry = gfx_make_quad_11(gfxcore); | ||
55 | if (!geometry) { | ||
56 | goto cleanup; | ||
57 | } | ||
58 | |||
59 | MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1}; | ||
60 | material_desc.uniforms[0] = (ShaderUniform){ | ||
61 | .type = UniformTexture, | ||
62 | .value.texture = texture, | ||
63 | .name = sstring_make("Texture")}; | ||
64 | Material* material = gfx_make_material(&material_desc); | ||
65 | if (!material) { | ||
66 | goto cleanup; | ||
67 | } | ||
68 | |||
69 | const MeshDesc mesh_desc = | ||
70 | (MeshDesc){.geometry = geometry, .material = material, .shader = shader}; | ||
71 | Mesh* mesh = gfx_make_mesh(&mesh_desc); | ||
72 | if (!mesh) { | ||
73 | goto cleanup; | ||
74 | } | ||
75 | |||
76 | SceneObject* object = | ||
77 | gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}}); | ||
78 | if (!object) { | ||
79 | goto cleanup; | ||
80 | } | ||
81 | |||
82 | if (!(state->scene = gfx_make_scene())) { | ||
83 | goto cleanup; | ||
84 | } | ||
85 | |||
86 | SceneNode* node = gfx_make_object_node(object); | ||
87 | if (!node) { | ||
88 | goto cleanup; | ||
89 | } | ||
90 | SceneNode* root = gfx_get_scene_root(state->scene); | ||
91 | if (!root) { | ||
92 | goto cleanup; | ||
93 | } | ||
94 | gfx_set_node_parent(node, root); | ||
95 | |||
96 | if (!(state->camera = gfx_make_camera())) { | ||
97 | goto cleanup; | ||
98 | } | ||
99 | |||
100 | *pp_state = state; | ||
101 | return true; | ||
102 | |||
103 | cleanup: | ||
104 | shutdown(game, state); | ||
105 | if (state) { | ||
106 | free(state); | ||
107 | } | ||
108 | return false; | ||
109 | } | ||
110 | |||
111 | void shutdown(Game* game, State* state) { | ||
112 | assert(game); | ||
113 | if (state) { | ||
114 | gfx_destroy_camera(&state->camera); | ||
115 | gfx_destroy_scene(&state->scene); | ||
116 | // State freed by plugin engine. | ||
117 | } | ||
118 | } | ||
119 | |||
120 | void render(const Game* game, const State* state) { | ||
121 | assert(game); | ||
122 | assert(state); | ||
123 | |||
124 | Renderer* renderer = gfx_get_renderer(game->gfx); | ||
125 | gfx_render_scene( | ||
126 | renderer, &(RenderSceneParams){ | ||
127 | .mode = RenderDefault, | ||
128 | .scene = state->scene, | ||
129 | .camera = state->camera}); | ||
130 | } | ||
131 | |||
132 | void resize(Game* game, State* state, int width, int height) { | ||
133 | assert(game); | ||
134 | assert(state); | ||
135 | |||
136 | const R fovy = 90 * TO_RAD; | ||
137 | const R aspect = (R)width / (R)height; | ||
138 | const R near = 0.1; | ||
139 | const R far = 1000; | ||
140 | const mat4 projection = mat4_perspective(fovy, aspect, near, far); | ||
141 | |||
142 | Camera* camera = gfx_get_camera_camera(state->camera); | ||
143 | camera->projection = projection; | ||
144 | } | ||
diff --git a/game/src/plugins/viewer.c b/game/src/plugins/viewer.c deleted file mode 100644 index 5fc4be7..0000000 --- a/game/src/plugins/viewer.c +++ /dev/null | |||
@@ -1,366 +0,0 @@ | |||
1 | #include "plugin.h" | ||
2 | |||
3 | #include <gfx/app.h> | ||
4 | #include <gfx/asset.h> | ||
5 | #include <gfx/renderer.h> | ||
6 | #include <gfx/scene.h> | ||
7 | #include <gfx/util/skyquad.h> | ||
8 | #include <math/camera.h> | ||
9 | #include <math/spatial3.h> | ||
10 | |||
11 | #include <log/log.h> | ||
12 | |||
13 | #include <stdlib.h> | ||
14 | |||
15 | // Paths to various scene files. | ||
16 | static const char* BOX = "/assets/models/box.gltf"; | ||
17 | static const char* SUZANNE = "/assets/models/suzanne.gltf"; | ||
18 | static const char* SPONZA = | ||
19 | "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf"; | ||
20 | static const char* FLIGHT_HELMET = | ||
21 | "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf"; | ||
22 | static const char* DAMAGED_HELMET = | ||
23 | "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf"; | ||
24 | static const char* GIRL = | ||
25 | "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf"; | ||
26 | static const char* BOXES = | ||
27 | "/home/jeanne/Nextcloud/assets/models/boxes/boxes.gltf"; | ||
28 | |||
29 | #define DEFAULT_SCENE_FILE GIRL | ||
30 | |||
31 | static const bool RenderBoundingBoxes = false; | ||
32 | static const R DefaultCameraSpeed = (R)6.0; | ||
33 | static const R DefaultMouseSensitivity = (R)(10 * TO_RAD); | ||
34 | static const vec3 DefaultCameraPosition = (vec3){0, 2, 5}; | ||
35 | |||
36 | typedef struct CameraCommand { | ||
37 | bool CameraMoveLeft : 1; | ||
38 | bool CameraMoveRight : 1; | ||
39 | bool CameraMoveForward : 1; | ||
40 | bool CameraMoveBackward : 1; | ||
41 | } CameraCommand; | ||
42 | |||
43 | typedef struct CameraController { | ||
44 | R camera_speed; // Camera movement speed. | ||
45 | R mouse_sensitivity; // Controls the degree with which mouse movements | ||
46 | // rotate the camera. | ||
47 | vec2 prev_mouse_position; // Mouse position in the previous frame. | ||
48 | bool rotating; // When true, subsequent mouse movements cause the | ||
49 | // camera to rotate. | ||
50 | } CameraController; | ||
51 | |||
52 | typedef struct State { | ||
53 | Scene* scene; | ||
54 | Model* model; | ||
55 | SceneCamera* camera; | ||
56 | CameraController camera_controller; | ||
57 | } State; | ||
58 | |||
59 | /// Load the skyquad texture. | ||
60 | static const Texture* load_environment_map(Gfx* gfx) { | ||
61 | assert(gfx); | ||
62 | return gfx_load_texture( | ||
63 | gfx, &(LoadTextureCmd){ | ||
64 | .origin = AssetFromFile, | ||
65 | .type = LoadCubemap, | ||
66 | .colour_space = sRGB, | ||
67 | .filtering = NearestFiltering, | ||
68 | .mipmaps = false, | ||
69 | .data.cubemap.filepaths = { | ||
70 | mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"), | ||
71 | mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"), | ||
72 | mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"), | ||
73 | mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"), | ||
74 | mstring_make("/assets/skybox/clouds1/clouds1_south.bmp"), | ||
75 | mstring_make("/assets/skybox/clouds1/clouds1_north.bmp")} | ||
76 | }); | ||
77 | } | ||
78 | |||
79 | /// Load the skyquad and return the environment light node. | ||
80 | static SceneNode* load_skyquad(Gfx* gfx, SceneNode* root) { | ||
81 | assert(gfx); | ||
82 | assert(root); | ||
83 | |||
84 | GfxCore* gfxcore = gfx_get_core(gfx); | ||
85 | |||
86 | const Texture* environment_map = load_environment_map(gfx); | ||
87 | if (!environment_map) { | ||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | return gfx_setup_skyquad(gfxcore, root, environment_map); | ||
92 | } | ||
93 | |||
94 | /// Load the 3D scene. | ||
95 | /// Return the loaded model. | ||
96 | static Model* load_scene(Game* game, State* state, const char* scene_filepath) { | ||
97 | assert(game); | ||
98 | assert(game->gfx); | ||
99 | assert(state); | ||
100 | assert(state->scene); | ||
101 | |||
102 | Camera* camera = gfx_get_camera_camera(state->camera); | ||
103 | spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2)); | ||
104 | |||
105 | SceneNode* root = gfx_get_scene_root(state->scene); | ||
106 | SceneNode* sky_light_node = load_skyquad(game->gfx, root); | ||
107 | if (!sky_light_node) { | ||
108 | return 0; // test | ||
109 | } | ||
110 | |||
111 | Model* model = gfx_load_model( | ||
112 | game->gfx, | ||
113 | &(LoadModelCmd){ | ||
114 | .origin = AssetFromFile, .filepath = mstring_make(scene_filepath)}); | ||
115 | if (!model) { | ||
116 | return 0; | ||
117 | } | ||
118 | SceneNode* model_node = gfx_make_model_node(model); | ||
119 | if (!model_node) { | ||
120 | return 0; | ||
121 | } | ||
122 | gfx_set_node_parent(model_node, sky_light_node); | ||
123 | |||
124 | gfx_log_node_hierarchy(root); | ||
125 | |||
126 | return model; | ||
127 | } | ||
128 | |||
129 | bool init(Game* game, State** pp_state) { | ||
130 | assert(game); | ||
131 | |||
132 | // Usage: <scene file> | ||
133 | const char* scene_filepath = | ||
134 | game->argc > 1 ? game->argv[1] : DEFAULT_SCENE_FILE; | ||
135 | |||
136 | State* state = calloc(1, sizeof(State)); | ||
137 | if (!state) { | ||
138 | goto cleanup; | ||
139 | } | ||
140 | |||
141 | if (!(state->scene = gfx_make_scene())) { | ||
142 | goto cleanup; | ||
143 | } | ||
144 | if (!(state->camera = gfx_make_camera())) { | ||
145 | goto cleanup; | ||
146 | } | ||
147 | |||
148 | state->model = load_scene(game, state, scene_filepath); | ||
149 | if (!state->model) { | ||
150 | goto cleanup; | ||
151 | } | ||
152 | |||
153 | Anima* anima = gfx_get_model_anima(state->model); | ||
154 | if (anima) { | ||
155 | gfx_play_animation( | ||
156 | anima, &(AnimationPlaySettings){.name = "Walk", .loop = true}); | ||
157 | // TODO: Interpolate animations. | ||
158 | /*gfx_play_animation( | ||
159 | anima, | ||
160 | &(AnimationPlaySettings){.name = "Jumping-jack-lower", .loop = true}); | ||
161 | gfx_play_animation( | ||
162 | anima, &(AnimationPlaySettings){ | ||
163 | .name = "Jumping-jack-arms-mid", .loop = true});*/ | ||
164 | } | ||
165 | |||
166 | spatial3_set_position( | ||
167 | &gfx_get_camera_camera(state->camera)->spatial, DefaultCameraPosition); | ||
168 | |||
169 | state->camera_controller.camera_speed = DefaultCameraSpeed; | ||
170 | state->camera_controller.mouse_sensitivity = DefaultMouseSensitivity; | ||
171 | |||
172 | *pp_state = state; | ||
173 | return true; | ||
174 | |||
175 | cleanup: | ||
176 | shutdown(game, state); | ||
177 | if (state) { | ||
178 | free(state); | ||
179 | } | ||
180 | return false; | ||
181 | } | ||
182 | |||
183 | void shutdown(Game* game, State* state) { | ||
184 | assert(game); | ||
185 | if (state) { | ||
186 | gfx_destroy_camera(&state->camera); | ||
187 | gfx_destroy_scene(&state->scene); | ||
188 | // State freed by plugin engine. | ||
189 | } | ||
190 | } | ||
191 | |||
192 | static void update_camera( | ||
193 | CameraController* controller, R dt, vec2 mouse_position, | ||
194 | CameraCommand command, Spatial3* camera) { | ||
195 | assert(controller); | ||
196 | assert(camera); | ||
197 | |||
198 | // Translation. | ||
199 | const R move_x = (R)(command.CameraMoveLeft ? -1 : 0) + | ||
200 | (R)(command.CameraMoveRight ? 1 : 0); | ||
201 | const R move_y = (R)(command.CameraMoveForward ? 1 : 0) + | ||
202 | (R)(command.CameraMoveBackward ? -1 : 0); | ||
203 | const vec2 translation = | ||
204 | vec2_scale(vec2_make(move_x, move_y), controller->camera_speed * dt); | ||
205 | spatial3_move_right(camera, translation.x); | ||
206 | spatial3_move_forwards(camera, translation.y); | ||
207 | |||
208 | // Rotation. | ||
209 | if (controller->rotating) { | ||
210 | const vec2 mouse_delta = | ||
211 | vec2_sub(mouse_position, controller->prev_mouse_position); | ||
212 | |||
213 | const vec2 rotation = | ||
214 | vec2_scale(mouse_delta, controller->mouse_sensitivity * dt); | ||
215 | |||
216 | spatial3_global_yaw(camera, -rotation.x); | ||
217 | spatial3_pitch(camera, -rotation.y); | ||
218 | } | ||
219 | |||
220 | // Update controller state. | ||
221 | controller->prev_mouse_position = mouse_position; | ||
222 | } | ||
223 | |||
224 | void update(Game* game, State* state, double t, double dt) { | ||
225 | assert(game); | ||
226 | assert(state); | ||
227 | assert(state->scene); | ||
228 | assert(state->camera); | ||
229 | |||
230 | double mouse_x, mouse_y; | ||
231 | gfx_app_get_mouse_position(&mouse_x, &mouse_y); | ||
232 | const vec2 mouse_position = {(R)mouse_x, (R)mouse_y}; | ||
233 | |||
234 | const CameraCommand camera_command = (CameraCommand){ | ||
235 | .CameraMoveLeft = gfx_app_is_key_pressed(KeyA), | ||
236 | .CameraMoveRight = gfx_app_is_key_pressed(KeyD), | ||
237 | .CameraMoveForward = gfx_app_is_key_pressed(KeyW), | ||
238 | .CameraMoveBackward = gfx_app_is_key_pressed(KeyS), | ||
239 | }; | ||
240 | |||
241 | state->camera_controller.rotating = gfx_app_is_mouse_button_pressed(LMB); | ||
242 | |||
243 | update_camera( | ||
244 | &state->camera_controller, (R)dt, mouse_position, camera_command, | ||
245 | &gfx_get_camera_camera(state->camera)->spatial); | ||
246 | |||
247 | // const vec3 orbit_point = vec3_make(0, 2, 0); | ||
248 | // Camera* camera = gfx_get_camera_camera(state->camera); | ||
249 | // spatial3_orbit( | ||
250 | // &camera->spatial, orbit_point, | ||
251 | // /*radius=*/5, | ||
252 | // /*azimuth=*/(R)(t * 0.5), /*zenith=*/0); | ||
253 | // spatial3_lookat(&camera->spatial, orbit_point); | ||
254 | |||
255 | gfx_update(state->scene, state->camera, (R)t); | ||
256 | } | ||
257 | |||
258 | /// Render the bounding boxes of all scene objects. | ||
259 | static void render_bounding_boxes_rec( | ||
260 | ImmRenderer* imm, const Anima* anima, const mat4* parent_model_matrix, | ||
261 | const SceneNode* node) { | ||
262 | assert(imm); | ||
263 | assert(node); | ||
264 | |||
265 | const mat4 model_matrix = | ||
266 | mat4_mul(*parent_model_matrix, gfx_get_node_transform(node)); | ||
267 | |||
268 | const NodeType node_type = gfx_get_node_type(node); | ||
269 | |||
270 | if (node_type == ModelNode) { | ||
271 | const Model* model = gfx_get_node_model(node); | ||
272 | const SceneNode* root = gfx_get_model_root(model); | ||
273 | render_bounding_boxes_rec(imm, anima, &model_matrix, root); | ||
274 | } else if (node_type == AnimaNode) { | ||
275 | anima = gfx_get_node_anima(node); | ||
276 | } else if (node_type == ObjectNode) { | ||
277 | gfx_imm_set_model_matrix(imm, &model_matrix); | ||
278 | |||
279 | const SceneObject* obj = gfx_get_node_object(node); | ||
280 | const Skeleton* skeleton = gfx_get_object_skeleton(obj); | ||
281 | |||
282 | if (skeleton) { // Animated model. | ||
283 | assert(anima); | ||
284 | const size_t num_joints = gfx_get_skeleton_num_joints(skeleton); | ||
285 | for (size_t i = 0; i < num_joints; ++i) { | ||
286 | if (gfx_joint_has_box(anima, skeleton, i)) { | ||
287 | const Box box = gfx_get_joint_box(anima, skeleton, i); | ||
288 | gfx_imm_draw_box3(imm, box.vertices); | ||
289 | } | ||
290 | } | ||
291 | } else { // Static model. | ||
292 | const aabb3 box = gfx_get_object_aabb(obj); | ||
293 | gfx_imm_draw_aabb3(imm, box); | ||
294 | } | ||
295 | } | ||
296 | |||
297 | // Render children's boxes. | ||
298 | const SceneNode* child = gfx_get_node_child(node); | ||
299 | while (child) { | ||
300 | render_bounding_boxes_rec(imm, anima, &model_matrix, child); | ||
301 | child = gfx_get_node_sibling(child); | ||
302 | } | ||
303 | } | ||
304 | |||
305 | /// Render the bounding boxes of all scene objects. | ||
306 | static void render_bounding_boxes(const Game* game, const State* state) { | ||
307 | assert(game); | ||
308 | assert(state); | ||
309 | |||
310 | GfxCore* gfxcore = gfx_get_core(game->gfx); | ||
311 | ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); | ||
312 | assert(gfxcore); | ||
313 | assert(imm); | ||
314 | |||
315 | const mat4 id = mat4_id(); | ||
316 | Anima* anima = 0; | ||
317 | |||
318 | gfx_set_blending(gfxcore, true); | ||
319 | gfx_set_depth_mask(gfxcore, false); | ||
320 | gfx_set_polygon_offset(gfxcore, -1.5f, -1.0f); | ||
321 | |||
322 | gfx_imm_start(imm); | ||
323 | gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera)); | ||
324 | gfx_imm_set_colour(imm, vec4_make(0.3, 0.3, 0.9, 0.1)); | ||
325 | render_bounding_boxes_rec(imm, anima, &id, gfx_get_scene_root(state->scene)); | ||
326 | gfx_imm_end(imm); | ||
327 | |||
328 | gfx_reset_polygon_offset(gfxcore); | ||
329 | gfx_set_depth_mask(gfxcore, true); | ||
330 | gfx_set_blending(gfxcore, false); | ||
331 | } | ||
332 | |||
333 | void render(const Game* game, const State* state) { | ||
334 | assert(state); | ||
335 | assert(game); | ||
336 | assert(game->gfx); | ||
337 | assert(state->scene); | ||
338 | assert(state->camera); | ||
339 | |||
340 | Renderer* renderer = gfx_get_renderer(game->gfx); | ||
341 | assert(renderer); | ||
342 | |||
343 | gfx_render_scene( | ||
344 | renderer, &(RenderSceneParams){ | ||
345 | .mode = RenderDefault, | ||
346 | .scene = state->scene, | ||
347 | .camera = state->camera}); | ||
348 | |||
349 | if (RenderBoundingBoxes) { | ||
350 | render_bounding_boxes(game, state); | ||
351 | } | ||
352 | } | ||
353 | |||
354 | void resize(Game* game, State* state, int width, int height) { | ||
355 | assert(game); | ||
356 | assert(state); | ||
357 | |||
358 | const R fovy = 60 * TO_RAD; | ||
359 | const R aspect = (R)width / (R)height; | ||
360 | const R near = 0.1; | ||
361 | const R far = 1000; | ||
362 | const mat4 projection = mat4_perspective(fovy, aspect, near, far); | ||
363 | |||
364 | Camera* camera = gfx_get_camera_camera(state->camera); | ||
365 | camera->projection = projection; | ||
366 | } | ||