aboutsummaryrefslogtreecommitdiff
path: root/game
diff options
context:
space:
mode:
Diffstat (limited to 'game')
-rw-r--r--game/CMakeLists.txt22
-rw-r--r--game/src/game.c223
-rw-r--r--game/src/game.h21
-rw-r--r--game/src/plugins/CMakeLists.txt29
-rw-r--r--game/src/plugins/plugin.h52
-rw-r--r--game/src/plugins/pong.c237
-rw-r--r--game/src/plugins/texture_view.c144
-rw-r--r--game/src/plugins/viewer.c366
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 @@
1cmake_minimum_required(VERSION 3.0)
2
3add_subdirectory(src/plugins)
4
5project(game)
6
7add_executable(game
8 src/game.c)
9
10target_include_directories(game PRIVATE
11 src/)
12
13target_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
38static const int WIDTH = 1350;
39static const int HEIGHT = 900;
40static const int MAX_FPS = 60;
41
42typedef struct GfxAppState {
43 Game game;
44} GfxAppState;
45
46/// Initialize the game's plugin.
47static 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.
64static 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.
75static 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.
86static 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.
97static 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.
107static 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
118static void Shutdown(Game* game);
119
120static 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
171cleanup:
172 LOGE("Gfx error: %s", get_error());
173 Shutdown(game);
174 return false;
175}
176
177static 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
191static 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
206static 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
213static 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
223GFX_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
6typedef struct PluginEngine PluginEngine;
7typedef struct Plugin Plugin;
8typedef struct Gfx Gfx;
9typedef struct Scene Scene;
10typedef struct SceneCamera SceneCamera;
11
12/// Game state.
13typedef 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 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(plugins)
4
5set(LINK_LIBRARIES cstring math gfx gfx-app)
6
7# Viewer
8
9add_library(viewer SHARED
10 viewer.c)
11
12target_link_libraries(viewer PUBLIC
13 ${LINK_LIBRARIES})
14
15# Texture viewer
16
17add_library(texture_view SHARED
18 texture_view.c)
19
20target_link_libraries(texture_view PUBLIC
21 ${LINK_LIBRARIES})
22
23# Pong
24
25add_library(pong SHARED
26 pong.c)
27
28target_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
13typedef 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.)
22bool 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.
30void 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.
35bool boot(Game*, State*);
36
37/// Update the plugin's and the game's state.
38void update(Game*, State*, double t, double dt);
39
40/// Render hook.
41void render(const Game*, const State*);
42
43/// Called when the game's window is resized.
44void resize(Game*, State*, int width, int height);
45
46// Signatures for the plugin's exposed functions.
47typedef bool (*plugin_init)(Game*, State**);
48typedef bool (*plugin_shutdown)(Game*, State*);
49typedef bool (*plugin_boot)(Game*, State*);
50typedef void (*plugin_update)(Game*, State*, double t, double dt);
51typedef void (*plugin_render)(const Game*, const State*);
52typedef 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
13static const vec2 PAD_SIZE = (vec2){120, 20};
14static const R PLAYER_Y_OFFSET = 50;
15static const R PLAYER_SPEED = 800;
16
17static const R ENEMY_SPEED = 2;
18
19static const R BALL_SIZE = 18;
20static const R BALL_SPEED = 360; // In each dimension.
21
22static const R EPS = (R)1e-3;
23
24typedef struct Player {
25 vec2 position;
26} Player;
27
28typedef struct Ball {
29 vec2 position;
30 vec2 velocity;
31} Ball;
32
33typedef struct State {
34 bool game_started;
35 Player human;
36 Player enemy;
37 Ball ball;
38 mat4 viewProjection;
39} State;
40
41bool 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
52cleanup:
53 free(state);
54 return false;
55}
56
57void shutdown(Game* game, State* state) {
58 assert(game);
59 assert(state);
60}
61
62static 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
91void 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
97void 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
111void 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
126void 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
154void 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
168static 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
181static 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
193void 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
208static R clamp_to_width(int width, R x, R extent) {
209 return min(x, (R)width - extent);
210}
211
212void 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.
16static const char* DEFAULT_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp";
17// static const char* DEFAULT_TEXTURE = "/assets/checkerboard.jpg";
18
19struct State {
20 Scene* scene;
21 SceneCamera* camera;
22};
23
24bool 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
103cleanup:
104 shutdown(game, state);
105 if (state) {
106 free(state);
107 }
108 return false;
109}
110
111void 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
120void 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
132void 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.
16static const char* BOX = "/assets/models/box.gltf";
17static const char* SUZANNE = "/assets/models/suzanne.gltf";
18static const char* SPONZA =
19 "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf";
20static const char* FLIGHT_HELMET =
21 "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf";
22static const char* DAMAGED_HELMET =
23 "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf";
24static const char* GIRL =
25 "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf";
26static const char* BOXES =
27 "/home/jeanne/Nextcloud/assets/models/boxes/boxes.gltf";
28
29#define DEFAULT_SCENE_FILE GIRL
30
31static const bool RenderBoundingBoxes = false;
32static const R DefaultCameraSpeed = (R)6.0;
33static const R DefaultMouseSensitivity = (R)(10 * TO_RAD);
34static const vec3 DefaultCameraPosition = (vec3){0, 2, 5};
35
36typedef struct CameraCommand {
37 bool CameraMoveLeft : 1;
38 bool CameraMoveRight : 1;
39 bool CameraMoveForward : 1;
40 bool CameraMoveBackward : 1;
41} CameraCommand;
42
43typedef 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
52typedef struct State {
53 Scene* scene;
54 Model* model;
55 SceneCamera* camera;
56 CameraController camera_controller;
57} State;
58
59/// Load the skyquad texture.
60static 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.
80static 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.
96static 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
129bool 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
175cleanup:
176 shutdown(game, state);
177 if (state) {
178 free(state);
179 }
180 return false;
181}
182
183void 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
192static 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
224void 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.
259static 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.
306static 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
333void 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
354void 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}