summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gfx/CMakeLists.txt2
-rw-r--r--gfx/include/gfx/scene.h10
-rw-r--r--gltfview/CMakeLists.txt7
-rw-r--r--gltfview/src/game.c279
-rw-r--r--gltfview/src/game.h16
-rw-r--r--gltfview/src/plugins/CMakeLists.txt17
-rw-r--r--gltfview/src/plugins/gltf_view.c167
-rw-r--r--gltfview/src/plugins/gltf_view.h9
-rw-r--r--gltfview/src/plugins/plugin.h49
-rw-r--r--gltfview/src/plugins/texture_view.c94
-rw-r--r--gltfview/src/plugins/texture_view.h9
11 files changed, 461 insertions, 198 deletions
diff --git a/gfx/CMakeLists.txt b/gfx/CMakeLists.txt
index 8a9bc8f..f5c9b5a 100644
--- a/gfx/CMakeLists.txt
+++ b/gfx/CMakeLists.txt
@@ -32,7 +32,7 @@ add_shader_library(shaders
32 shaders/view_texture.frag 32 shaders/view_texture.frag
33 shaders/view_texture.vert) 33 shaders/view_texture.vert)
34 34
35add_library(gfx 35add_library(gfx SHARED
36 src/render/buffer.c 36 src/render/buffer.c
37 src/render/framebuffer.c 37 src/render/framebuffer.c
38 src/render/geometry.c 38 src/render/geometry.c
diff --git a/gfx/include/gfx/scene.h b/gfx/include/gfx/scene.h
new file mode 100644
index 0000000..1d74679
--- /dev/null
+++ b/gfx/include/gfx/scene.h
@@ -0,0 +1,10 @@
1#pragma once
2
3#include <gfx/scene/animation.h>
4#include <gfx/scene/camera.h>
5#include <gfx/scene/light.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>
10#include <gfx/scene/scene.h>
diff --git a/gltfview/CMakeLists.txt b/gltfview/CMakeLists.txt
index 19ea0e5..0b0c3cc 100644
--- a/gltfview/CMakeLists.txt
+++ b/gltfview/CMakeLists.txt
@@ -1,5 +1,7 @@
1cmake_minimum_required(VERSION 3.0) 1cmake_minimum_required(VERSION 3.0)
2 2
3add_subdirectory(src/plugins)
4
3project(gltfview) 5project(gltfview)
4 6
5add_executable(gltfview 7add_executable(gltfview
@@ -10,8 +12,11 @@ target_include_directories(gltfview PRIVATE
10 src/) 12 src/)
11 13
12target_link_libraries(gltfview PRIVATE 14target_link_libraries(gltfview PRIVATE
15 cstring
13 gfx 16 gfx
14 gfx-app 17 gfx-app
18 list
15 log 19 log
16 math 20 math
17 mempool) 21 mempool
22 plugin)
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;
diff --git a/gltfview/src/game.h b/gltfview/src/game.h
index 4aeb5ea..2a7b7ef 100644
--- a/gltfview/src/game.h
+++ b/gltfview/src/game.h
@@ -9,17 +9,21 @@
9 9
10#include <stdbool.h> 10#include <stdbool.h>
11 11
12typedef struct Plugin Plugin;
13typedef struct PluginEngine PluginEngine;
14
12/// The delta time the game should be updated with. 15/// The delta time the game should be updated with.
13static const double game_dt = 1.0 / 60.0; 16static const double game_dt = 1.0 / 60.0;
14 17
15/// Game state. 18/// Game state.
16typedef struct { 19typedef struct {
17 Gfx* gfx; 20 int argc;
18 RenderBackend* render_backend; 21 const char** argv;
19 Renderer* renderer; 22 PluginEngine* plugin_engine;
20 Scene* scene; 23 Plugin* plugin;
21 SceneCamera* camera; 24 Gfx* gfx;
22 SceneNode* root_node; 25 Scene* scene;
26 SceneCamera* camera;
23} Game; 27} Game;
24 28
25bool game_new(Game*, int argc, const char** argv); 29bool game_new(Game*, int argc, const char** argv);
diff --git a/gltfview/src/plugins/CMakeLists.txt b/gltfview/src/plugins/CMakeLists.txt
new file mode 100644
index 0000000..ecb2a45
--- /dev/null
+++ b/gltfview/src/plugins/CMakeLists.txt
@@ -0,0 +1,17 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(plugins)
4
5set(LINK_LIBRARIES cstring math gfx)
6
7add_library(gltf_view SHARED
8 gltf_view.c)
9
10add_library(texture_view SHARED
11 texture_view.c)
12
13target_link_libraries(gltf_view PUBLIC
14 ${LINK_LIBRARIES})
15
16target_link_libraries(texture_view PUBLIC
17 ${LINK_LIBRARIES})
diff --git a/gltfview/src/plugins/gltf_view.c b/gltfview/src/plugins/gltf_view.c
new file mode 100644
index 0000000..511c2e8
--- /dev/null
+++ b/gltfview/src/plugins/gltf_view.c
@@ -0,0 +1,167 @@
1#include "gltf_view.h"
2
3#include <gfx/renderer.h>
4#include <gfx/util/scene.h>
5#include <gfx/util/skyquad.h>
6#include <gfx/util/texture.h>
7#include <math/camera.h>
8#include <math/spatial3.h>
9
10#include <stdlib.h>
11
12// Paths to various scene files.
13/*static const char* BOX = "/assets/models/box.gltf";
14static const char* SUZANNE = "/assets/models/suzanne.gltf";
15static const char* SPONZA =
16 "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf";
17static const char* FLIGHT_HELMET =
18 "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf";
19static const char* DAMAGED_HELMET =
20 "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf";*/
21static const char* GIRL =
22 "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf";
23
24#define DEFAULT_SCENE_FILE GIRL
25
26/// Load the skyquad texture.
27static Texture* load_environment_map(RenderBackend* render_backend) {
28 return gfx_load_texture(
29 render_backend,
30 &(LoadTextureCmd){
31 .origin = TextureFromFile,
32 .type = LoadCubemap,
33 .colour_space = sRGB,
34 .filtering = NearestFiltering,
35 .mipmaps = false,
36 .data.cubemap.filepaths = {
37 mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"),
38 mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"),
39 mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"),
40 mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"),
41 mstring_make("/assets/skybox/clouds1/clouds1_south.bmp"),
42 mstring_make("/assets/skybox/clouds1/clouds1_north.bmp")}
43 });
44}
45
46/// Load the skyquad and return the environment light node.
47static SceneNode* load_skyquad(RenderBackend* render_backend, SceneNode* root) {
48 assert(render_backend);
49 assert(root);
50
51 Texture* environment_map = load_environment_map(render_backend);
52 if (!environment_map) {
53 return 0;
54 }
55
56 return gfx_setup_skyquad(render_backend, root, environment_map);
57}
58
59/// Load the 3D scene.
60static SceneNode* load_scene(Game* game, const char* scene_filepath) {
61 assert(game);
62 assert(game->gfx);
63 assert(game->scene);
64
65 SceneNode* root = gfx_get_scene_root(game->scene);
66 RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
67
68 Camera* camera = gfx_get_camera_camera(game->camera);
69 spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2));
70
71 SceneNode* sky_light_node = load_skyquad(render_backend, root);
72 if (!sky_light_node) {
73 return 0;
74 }
75
76 SceneNode* scene_node = gfx_load_scene(
77 game->gfx, sky_light_node,
78 &(LoadSceneCmd){.origin = SceneFromFile, .filepath = scene_filepath});
79 if (!scene_node) {
80 return 0;
81 }
82
83 gfx_log_node_hierarchy(root);
84
85 return scene_node;
86}
87
88State* init(Game* game) {
89 assert(game);
90
91 State* state = calloc(1, sizeof(State));
92 return state;
93}
94
95bool boot(State* state, Game* game) {
96 assert(state);
97 assert(game);
98
99 const int argc = game->argc;
100 const char** argv = game->argv;
101
102 // Usage: <scene file>
103 const char* scene_filepath = argc > 1 ? argv[1] : DEFAULT_SCENE_FILE;
104
105 SceneNode* node = load_scene(game, scene_filepath);
106 if (!node) {
107 return false;
108 }
109 Anima* anima = gfx_get_node_anima(node);
110 gfx_play_animation(
111 anima, &(AnimationPlaySettings){.name = "Walk", .loop = true});
112
113 return true;
114}
115
116void update(State* state, Game* game, double t, double dt) {
117 assert(state);
118 assert(game);
119 assert(game->scene);
120 assert(game->camera);
121
122 gfx_animate_scene(game->scene, (R)t);
123
124 const vec3 orbit_point = vec3_make(0, 2, 0);
125 Camera* camera = gfx_get_camera_camera(game->camera);
126 spatial3_orbit(
127 &camera->spatial, orbit_point,
128 /*radius=*/2.5,
129 /*azimuth=*/t * 0.5, /*zenith=*/0);
130 spatial3_lookat(&camera->spatial, orbit_point);
131}
132
133/// Render the bounding boxes of all scene objects.
134static void render_bounding_boxes(ImmRenderer* imm, const SceneNode* node) {
135 if (gfx_get_node_type(node) == ObjectNode) {
136 // TODO: Look at the scene log. The JointNodes are detached from the
137 // ObjectNodes. This is why the boxes are not being transformed as expected
138 // here. Anima needs to animate boxes? Use OOBB in addition to AABB?
139 const mat4 model = gfx_get_node_global_transform(node);
140 const SceneObject* obj = gfx_get_node_object(node);
141 const aabb3 box = gfx_calc_object_aabb(obj);
142 gfx_imm_set_model_matrix(imm, &model);
143 gfx_imm_draw_aabb(imm, box);
144 }
145
146 // Render children's boxes.
147 for (NodeIter it = gfx_get_node_child(node); it;
148 it = gfx_get_next_child(it)) {
149 render_bounding_boxes(imm, gfx_get_iter_node(it));
150 }
151}
152
153void render(State* state, const Game* game) {
154 assert(state);
155 assert(game);
156 assert(game->gfx);
157 assert(game->scene);
158 assert(game->camera);
159
160 ImmRenderer* imm = gfx_get_imm_renderer(game->gfx);
161 assert(imm);
162 gfx_imm_start(imm);
163 gfx_imm_set_camera(imm, gfx_get_camera_camera(game->camera));
164 gfx_imm_set_colour(imm, vec4_make(0.2, 0.2, 1.0, 0.3));
165 render_bounding_boxes(imm, gfx_get_scene_root(game->scene));
166 gfx_imm_end(imm);
167}
diff --git a/gltfview/src/plugins/gltf_view.h b/gltfview/src/plugins/gltf_view.h
new file mode 100644
index 0000000..670d88d
--- /dev/null
+++ b/gltfview/src/plugins/gltf_view.h
@@ -0,0 +1,9 @@
1#pragma once
2
3#include "plugin.h"
4
5#include <gfx/scene.h>
6
7typedef struct State {
8 int unused;
9} State;
diff --git a/gltfview/src/plugins/plugin.h b/gltfview/src/plugins/plugin.h
new file mode 100644
index 0000000..0e0e12c
--- /dev/null
+++ b/gltfview/src/plugins/plugin.h
@@ -0,0 +1,49 @@
1/*
2 * Game plugin.
3 *
4 * A game plugin exposes three functions:
5 * - boot(): called once when the plugin is first loaded during the lifetime of
6 * the game.
7 * - init() -> state: creates and returns the plugin's state.
8 * - update(state): takes and updates the state, possibly with side effects.
9 * - render(): performs custom rendering.
10 *
11 * boot() is convenient for one-time initialization of the scene.
12 *
13 * init() is called every time the plugin is loaded. It is assumed that the
14 * plugin's state is encapsulated in the object returned.
15 *
16 * update() updates the plugin state and has side effects on the scene. It is
17 * assumed that update does not reference any global, mutable state outside of
18 * the scene and the plugin state returned by init().
19 */
20#pragma once
21
22#include "../game.h"
23
24#include <gfx/gfx.h>
25#include <gfx/scene.h>
26
27#include <stdbool.h>
28
29typedef struct State State;
30
31/// Initialize the plugin's state.
32State* init(Game*);
33
34/// Function called the first time the plugin is loaded throughout the
35/// application's lifetime. Allows the plugin to do one-time initialization of
36/// the game state.
37bool boot(State*, Game*);
38
39/// Update the plugin's and the game's state.
40void update(State*, Game*, double t, double dt);
41
42/// Optional plugin rendering hook.
43void render(State*, const Game*);
44
45// Signatures for the plugin's exposed functions.
46typedef void* (*plugin_init)(Game*);
47typedef bool (*plugin_boot)(State*, Game*);
48typedef void (*plugin_update)(State*, Game*, double t, double dt);
49typedef void (*plugin_render)(State*, const Game*);
diff --git a/gltfview/src/plugins/texture_view.c b/gltfview/src/plugins/texture_view.c
new file mode 100644
index 0000000..f2c650f
--- /dev/null
+++ b/gltfview/src/plugins/texture_view.c
@@ -0,0 +1,94 @@
1#include "texture_view.h"
2
3#include <gfx/render_backend.h>
4#include <gfx/util/geometry.h>
5#include <gfx/util/shader.h>
6#include <gfx/util/texture.h>
7
8#include <math/camera.h>
9
10#include <assert.h>
11#include <stdlib.h>
12
13// Default texture to load if no texture is provided.
14static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp";
15
16State* init(Game* game) {
17 assert(game);
18
19 State* state = calloc(1, sizeof(State));
20 return state;
21}
22
23bool boot(State* state, Game* game) {
24 assert(state);
25 assert(game);
26
27 // Usage: [texture file]
28 const char* texture_file = game->argc > 1 ? game->argv[1] : CLOUDS1_TEXTURE;
29
30 RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
31
32 Texture* texture = gfx_load_texture(
33 render_backend, &(LoadTextureCmd){
34 .origin = TextureFromFile,
35 .type = LoadTexture,
36 .filtering = LinearFiltering,
37 .mipmaps = false,
38 .data.texture.filepath = mstring_make(texture_file)});
39
40 Camera* camera = gfx_get_camera_camera(game->camera);
41 spatial3_set_position(&camera->spatial, vec3_make(0, 0, 1));
42
43 ShaderProgram* shader = gfx_make_view_texture_shader(render_backend);
44 if (!shader) {
45 return false;
46 }
47
48 Geometry* geometry = gfx_make_quad_11(render_backend);
49 if (!geometry) {
50 return false;
51 }
52
53 MaterialDesc material_desc = (MaterialDesc){0};
54 material_desc.uniforms[0] = (ShaderUniform){
55 .type = UniformTexture,
56 .value.texture = texture,
57 .name = sstring_make("Texture")};
58 material_desc.num_uniforms = 1;
59 Material* material = gfx_make_material(&material_desc);
60 if (!material) {
61 return false;
62 }
63
64 MeshDesc mesh_desc = (MeshDesc){0};
65 mesh_desc.geometry = geometry;
66 mesh_desc.material = material;
67 mesh_desc.shader = shader;
68 Mesh* mesh = gfx_make_mesh(&mesh_desc);
69 if (!mesh) {
70 return false;
71 }
72
73 SceneObject* object = gfx_make_object();
74 if (!object) {
75 return false;
76 }
77 gfx_add_object_mesh(object, mesh);
78
79 SceneNode* node = gfx_make_object_node(object);
80 SceneNode* root = gfx_get_scene_root(game->scene);
81 gfx_set_node_parent(node, root);
82
83 return true;
84}
85
86void update(State* state, Game* game, double t, double dt) {
87 assert(state);
88 assert(game);
89}
90
91void render(State* state, const Game* game) {
92 assert(state);
93 assert(game);
94}
diff --git a/gltfview/src/plugins/texture_view.h b/gltfview/src/plugins/texture_view.h
new file mode 100644
index 0000000..670d88d
--- /dev/null
+++ b/gltfview/src/plugins/texture_view.h
@@ -0,0 +1,9 @@
1#pragma once
2
3#include "plugin.h"
4
5#include <gfx/scene.h>
6
7typedef struct State {
8 int unused;
9} State;