summaryrefslogtreecommitdiff
path: root/gltfview/src/game.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-06-16 09:38:15 -0700
committer3gg <3gg@shellblade.net>2023-06-16 09:38:15 -0700
commit520e4e67cd9ff53f3c3512c80d07193625e07e3e (patch)
treef2f8acfc2eb0d2aa279263d93af00beef7e93a1b /gltfview/src/game.c
parent14e6edd6bfe94089d52b5c4b6899dea23923e9be (diff)
New plugin architecture.
Diffstat (limited to 'gltfview/src/game.c')
-rw-r--r--gltfview/src/game.c279
1 files changed, 89 insertions, 190 deletions
diff --git a/gltfview/src/game.c b/gltfview/src/game.c
index c80a1b5..6d8430b 100644
--- a/gltfview/src/game.c
+++ b/gltfview/src/game.c
@@ -1,258 +1,157 @@
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
1#include "game.h" 9#include "game.h"
10#include "plugins/plugin.h"
2 11
3#include <gfx/error.h> 12#include <gfx/error.h>
4#include <gfx/render_backend.h> 13#include <gfx/render_backend.h>
5#include <gfx/scene/camera.h> 14#include <gfx/scene/camera.h>
6#include <gfx/scene/material.h>
7#include <gfx/scene/mesh.h>
8#include <gfx/scene/node.h>
9#include <gfx/scene/object.h> 15#include <gfx/scene/object.h>
10#include <gfx/util/geometry.h>
11#include <gfx/util/scene.h>
12#include <gfx/util/shader.h>
13#include <gfx/util/skyquad.h>
14#include <gfx/util/texture.h>
15 16
16#include <log/log.h> 17#include <log/log.h>
17#include <math/camera.h> 18#include <math/camera.h>
19#include <plugin.h>
18 20
19#include <assert.h> 21#include <assert.h>
20#include <stdbool.h> 22#include <stdbool.h>
21#include <stdio.h> 23#include <stdio.h>
22 24
23// Paths to various scene files. 25#include <linux/limits.h>
24static const char* BOX = "/assets/models/box.gltf";
25static const char* SUZANNE = "/assets/models/suzanne.gltf";
26static const char* SPONZA =
27 "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf";
28static const char* FLIGHT_HELMET =
29 "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf";
30static const char* DAMAGED_HELMET =
31 "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf";
32static const char* GIRL =
33 "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf";
34
35#define DEFAULT_SCENE_FILE GIRL
36 26
37static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; 27#include <unistd.h>
38 28
39/// Load the skyquad texture. 29#undef _GNU_SOURCE
40static Texture* load_environment_map(RenderBackend* render_backend) {
41 return gfx_load_texture(
42 render_backend,
43 &(LoadTextureCmd){
44 .origin = TextureFromFile,
45 .type = LoadCubemap,
46 .colour_space = sRGB,
47 .filtering = NearestFiltering,
48 .mipmaps = false,
49 .data.cubemap.filepaths = {
50 mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"),
51 mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"),
52 mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"),
53 mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"),
54 mstring_make("/assets/skybox/clouds1/clouds1_south.bmp"),
55 mstring_make("/assets/skybox/clouds1/clouds1_north.bmp")}
56 });
57}
58 30
59/// Load the skyquad and return the environment light node. 31// Plugin to load if no plugin is provided.
60static SceneNode* load_skyquad(Game* game) { 32static const char* DEFAULT_PLUGIN = "texture_view";
61 assert(game);
62 33
63 Texture* environment_map = load_environment_map(game->render_backend); 34static bool validate_plugin(const Plugin* plugin) {
64 if (!environment_map) { 35#define CHECK_FUNCTION(name, signature) \
65 return 0; 36 if (!plugin_resolve(plugin, signature, name)) { \
37 LOGE("Plugin is missing function: " #name); \
38 return false; \
66 } 39 }
67 40 CHECK_FUNCTION("init", plugin_init);
68 return gfx_setup_skyquad( 41 CHECK_FUNCTION("boot", plugin_boot);
69 game->gfx, gfx_get_scene_root(game->scene), environment_map); 42 CHECK_FUNCTION("update", plugin_update);
70} 43 CHECK_FUNCTION("render", plugin_render);
71 44 return true;
72/// Load the 3D scene.
73static SceneNode* load_scene(
74 Game* game, const char* scene_filepath, const char* view_mode) {
75 assert(game);
76
77 game->camera = gfx_make_camera();
78 if (!game->camera) {
79 return 0;
80 }
81 Camera* camera = gfx_get_camera_camera(game->camera);
82 spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2));
83
84 SceneNode* sky_light_node = load_skyquad(game);
85 if (!sky_light_node) {
86 return 0;
87 }
88
89 SceneNode* scene_node = gfx_load_scene(
90 game->gfx, sky_light_node,
91 &(LoadSceneCmd){.origin = SceneFromFile, .filepath = scene_filepath});
92 if (!scene_node) {
93 return 0;
94 }
95
96 gfx_log_node_hierarchy(gfx_get_scene_root(game->scene));
97
98 return scene_node;
99} 45}
100 46
101/// Load a scene for debugging textures. 47bool game_new(Game* game, int argc, const char** argv) {
102static bool load_texture_debugger_scene(Game* game) {
103 assert(game); 48 assert(game);
104 49
105 Texture* texture = gfx_load_texture( 50 // Syntax: game [plugin] <plugin args>
106 game->render_backend, 51 //
107 &(LoadTextureCmd){ 52 // Here we consume the [plugin] arg so that plugins receive the remainder
108 .origin = TextureFromFile, 53 // args starting from 0.
109 .type = LoadTexture, 54 game->argc = argc - 1;
110 .filtering = LinearFiltering, 55 game->argv = argv + 1;
111 .mipmaps = false,
112 .data.texture.filepath = mstring_make(CLOUDS1_TEXTURE)});
113 56
114 game->camera = gfx_make_camera(); 57 char exe_path_buf[NAME_MAX] = {0};
115 if (!game->camera) { 58 if (readlink("/proc/self/exe", exe_path_buf, sizeof(exe_path_buf)) == -1) {
116 return false; 59 LOGE("readlink(/proc/self/exe) failed");
60 goto cleanup;
117 } 61 }
118 Camera* camera = gfx_get_camera_camera(game->camera);
119 spatial3_set_position(&camera->spatial, vec3_make(0, 0, 1));
120 62
121 ShaderProgram* shader = gfx_make_view_texture_shader(game->render_backend); 63 // Replace the last / with a null terminator to remove the exe file from the
122 if (!shader) { 64 // path. This gets the file's parent directory.
123 return false; 65 *strrchr(exe_path_buf, '/') = 0;
124 }
125 66
126 Geometry* geometry = gfx_make_quad_11(game->render_backend); 67 const mstring exe_dir = mstring_make(exe_path_buf);
127 if (!geometry) { 68 const mstring plugins_path = mstring_concat_cstr(exe_dir, "/src/plugins");
128 return false;
129 }
130 69
131 MaterialDesc material_desc = (MaterialDesc){0}; 70 if (!(game->plugin_engine = new_plugin_engine(
132 material_desc.uniforms[0] = (ShaderUniform){ 71 &(PluginEngineDesc){.plugins_dir = mstring_cstr(&plugins_path)}))) {
133 .type = UniformTexture, 72 goto cleanup;
134 .value.texture = texture,
135 .name = sstring_make("Texture")};
136 material_desc.num_uniforms = 1;
137 Material* material = gfx_make_material(&material_desc);
138 if (!material) {
139 return false;
140 } 73 }
141 74
142 MeshDesc mesh_desc = (MeshDesc){0}; 75 const char* plugin = argc > 1 ? argv[1] : DEFAULT_PLUGIN;
143 mesh_desc.geometry = geometry; 76 if (!(game->plugin = load_plugin(game->plugin_engine, plugin))) {
144 mesh_desc.material = material; 77 goto cleanup;
145 mesh_desc.shader = shader;
146 Mesh* mesh = gfx_make_mesh(&mesh_desc);
147 if (!mesh) {
148 return false;
149 } 78 }
150 79 if (!validate_plugin(game->plugin)) {
151 SceneObject* object = gfx_make_object(); 80 goto cleanup;
152 if (!object) {
153 return false;
154 } 81 }
155 gfx_add_object_mesh(object, mesh);
156 82
157 SceneNode* node = gfx_make_object_node(object); 83 if (!(game->gfx = gfx_init())) {
158 SceneNode* root = gfx_get_scene_root(game->scene); 84 goto cleanup;
159 gfx_set_node_parent(node, root);
160
161 return true;
162}
163
164/// Render the bounding boxes of all scene objects.
165static void render_bounding_boxes(ImmRenderer* imm, const SceneNode* node) {
166 if (gfx_get_node_type(node) == ObjectNode) {
167 // TODO: Look at the scene log. The JointNodes are detached from the
168 // ObjectNodes. This is why the boxes are not being transformed as expected
169 // here. Anima needs to animate boxes? Use OOBB in addition to AABB?
170 const mat4 model = gfx_get_node_global_transform(node);
171 const SceneObject* obj = gfx_get_node_object(node);
172 const aabb3 box = gfx_calc_object_aabb(obj);
173 gfx_imm_set_model_matrix(imm, &model);
174 gfx_imm_draw_aabb(imm, box);
175 } 85 }
176 86 if (!(game->scene = gfx_make_scene())) {
177 // Render children's boxes. 87 goto cleanup;
178 for (NodeIter it = gfx_get_node_child(node); it;
179 it = gfx_get_next_child(it)) {
180 render_bounding_boxes(imm, gfx_get_iter_node(it));
181 } 88 }
182} 89 if (!(game->camera = gfx_make_camera())) {
183
184bool game_new(Game* game, int argc, const char** argv) {
185 // TODO: getopt() to implement proper argument parsing.
186 const char* scene_filepath = argc > 1 ? argv[1] : DEFAULT_SCENE_FILE;
187 const char* view_mode = argc > 2 ? argv[2] : "";
188
189 game->gfx = gfx_init();
190 if (!game->gfx) {
191 goto cleanup; 90 goto cleanup;
192 } 91 }
193 92
194 game->render_backend = gfx_get_render_backend(game->gfx); 93 void* plugin_state = plugin_call(game->plugin, plugin_init, "init", game);
195 game->renderer = gfx_get_renderer(game->gfx); 94 if (!plugin_state) {
196
197 game->scene = gfx_make_scene();
198 if (!game->scene) {
199 goto cleanup; 95 goto cleanup;
200 } 96 }
97 set_plugin_state(game->plugin, plugin_state);
201 98
202 game->root_node = load_scene(game, scene_filepath, view_mode); 99 bool boot_success =
203 if (!game->root_node) { 100 plugin_call(game->plugin, plugin_boot, "boot", plugin_state, game);
101 if (!boot_success) {
204 goto cleanup; 102 goto cleanup;
205 } 103 }
206 /*if (!load_texture_debugger_scene(game)) {
207 goto cleanup;
208 }*/
209
210 Anima* anima = gfx_get_node_anima(game->root_node);
211
212 gfx_play_animation(
213 anima, &(AnimationPlaySettings){.name = "Walk", .loop = true});
214 104
215 return true; 105 return true;
216 106
217cleanup: 107cleanup:
218 LOGE("Gfx error: %s", gfx_get_error()); 108 LOGE("Gfx error: %s", gfx_get_error());
109 game_end(game);
110 return false;
111}
112
113void game_end(Game* game) {
114 assert(game);
219 if (game->gfx) { 115 if (game->gfx) {
220 gfx_destroy(&game->gfx); 116 gfx_destroy(&game->gfx);
221 } 117 }
222 return false; 118 if (game->plugin) {
119 delete_plugin(&game->plugin);
120 }
121 if (game->plugin_engine) {
122 delete_plugin_engine(&game->plugin_engine);
123 }
223} 124}
224 125
225void game_end(Game* game) { gfx_destroy(&game->gfx); }
226
227void game_update(Game* game, double t, double dt) { 126void game_update(Game* game, double t, double dt) {
228 gfx_animate_scene(game->scene, (R)t); 127 plugin_engine_update(game->plugin_engine);
128 if (plugin_reloaded(game->plugin)) {
129 void* plugin_state = plugin_call(game->plugin, plugin_init, "init", game);
130 assert(plugin_state); // TODO: handle error better.
131 set_plugin_state(game->plugin, plugin_state);
132 }
229 133
230 const vec3 orbit_point = vec3_make(0, 2, 0); 134 void* plugin_state = get_plugin_state(game->plugin);
231 Camera* camera = gfx_get_camera_camera(game->camera); 135 assert(plugin_state);
232 spatial3_orbit( 136 plugin_call(game->plugin, plugin_update, "update", plugin_state, game, t, dt);
233 &camera->spatial, orbit_point,
234 /*radius=*/2.5,
235 /*azimuth=*/t * 0.5, /*zenith=*/0);
236 spatial3_lookat(&camera->spatial, orbit_point);
237} 137}
238 138
239void game_render(const Game* game) { 139void game_render(const Game* game) {
140 Renderer* renderer = gfx_get_renderer(game->gfx);
141
240 gfx_render_scene( 142 gfx_render_scene(
241 game->renderer, 143 renderer,
242 &(RenderSceneParams){ 144 &(RenderSceneParams){
243 .mode = RenderDefault, .scene = game->scene, .camera = game->camera}); 145 .mode = RenderDefault, .scene = game->scene, .camera = game->camera});
244 146
245 ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); 147 void* plugin_state = get_plugin_state(game->plugin);
246 assert(imm); 148 assert(plugin_state);
247 gfx_imm_start(imm); 149 plugin_call(game->plugin, plugin_render, "render", plugin_state, game);
248 gfx_imm_set_camera(imm, gfx_get_camera_camera(game->camera));
249 gfx_imm_set_colour(imm, vec4_make(0.2, 0.2, 1.0, 0.3));
250 render_bounding_boxes(imm, gfx_get_scene_root(game->scene));
251 gfx_imm_end(imm);
252} 150}
253 151
254void game_set_viewport(Game* game, int width, int height) { 152void game_set_viewport(Game* game, int width, int height) {
255 gfx_set_viewport(game->render_backend, width, height); 153 RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
154 gfx_set_viewport(render_backend, width, height);
256 155
257 const R fovy = 90 * TO_RAD; 156 const R fovy = 90 * TO_RAD;
258 const R aspect = (R)width / (R)height; 157 const R aspect = (R)width / (R)height;