summaryrefslogtreecommitdiff
path: root/game/src/plugins/viewer.c
diff options
context:
space:
mode:
Diffstat (limited to 'game/src/plugins/viewer.c')
-rw-r--r--game/src/plugins/viewer.c240
1 files changed, 240 insertions, 0 deletions
diff --git a/game/src/plugins/viewer.c b/game/src/plugins/viewer.c
new file mode 100644
index 0000000..83fc8ed
--- /dev/null
+++ b/game/src/plugins/viewer.c
@@ -0,0 +1,240 @@
1#include "plugin.h"
2
3#include <gfx/renderer.h>
4#include <gfx/scene.h>
5#include <gfx/util/scene.h>
6#include <gfx/util/skyquad.h>
7#include <gfx/util/texture.h>
8#include <math/camera.h>
9#include <math/spatial3.h>
10
11#include <stdlib.h>
12
13// Paths to various scene files.
14static const char* BOX = "/assets/models/box.gltf";
15static const char* SUZANNE = "/assets/models/suzanne.gltf";
16static const char* SPONZA =
17 "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf";
18static const char* FLIGHT_HELMET =
19 "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf";
20static const char* DAMAGED_HELMET =
21 "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf";
22static const char* GIRL =
23 "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf";
24
25#define DEFAULT_SCENE_FILE GIRL
26
27struct State {
28 Scene* scene;
29 SceneCamera* camera;
30};
31
32/// Load the skyquad texture.
33static Texture* load_environment_map(RenderBackend* render_backend) {
34 return gfx_load_texture(
35 render_backend,
36 &(LoadTextureCmd){
37 .origin = TextureFromFile,
38 .type = LoadCubemap,
39 .colour_space = sRGB,
40 .filtering = NearestFiltering,
41 .mipmaps = false,
42 .data.cubemap.filepaths = {
43 mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"),
44 mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"),
45 mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"),
46 mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"),
47 mstring_make("/assets/skybox/clouds1/clouds1_south.bmp"),
48 mstring_make("/assets/skybox/clouds1/clouds1_north.bmp")}
49 });
50}
51
52/// Load the skyquad and return the environment light node.
53static SceneNode* load_skyquad(RenderBackend* render_backend, SceneNode* root) {
54 assert(render_backend);
55 assert(root);
56
57 Texture* environment_map = load_environment_map(render_backend);
58 if (!environment_map) {
59 return 0;
60 }
61
62 return gfx_setup_skyquad(render_backend, root, environment_map);
63}
64
65/// Load the 3D scene.
66static SceneNode* load_scene(
67 Game* game, State* state, const char* scene_filepath) {
68 assert(game);
69 assert(game->gfx);
70 assert(state);
71 assert(state->scene);
72
73 SceneNode* root = gfx_get_scene_root(state->scene);
74 RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
75
76 Camera* camera = gfx_get_camera_camera(state->camera);
77 spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2));
78
79 SceneNode* sky_light_node = load_skyquad(render_backend, root);
80 if (!sky_light_node) {
81 return 0;
82 }
83
84 SceneNode* scene_node = gfx_load_scene(
85 game->gfx, sky_light_node,
86 &(LoadSceneCmd){.origin = SceneFromFile, .filepath = scene_filepath});
87 if (!scene_node) {
88 return 0;
89 }
90
91 gfx_log_node_hierarchy(root);
92
93 return scene_node;
94}
95
96bool init(Game* game, State** pp_state) {
97 assert(game);
98
99 State* state = calloc(1, sizeof(State));
100 if (!state) {
101 goto cleanup;
102 }
103
104 if (!(state->scene = gfx_make_scene())) {
105 goto cleanup;
106 }
107 if (!(state->camera = gfx_make_camera())) {
108 goto cleanup;
109 }
110
111 const int argc = game->argc;
112 const char** argv = game->argv;
113
114 // Usage: <scene file>
115 const char* scene_filepath = argc > 1 ? argv[1] : DEFAULT_SCENE_FILE;
116
117 SceneNode* node = load_scene(game, state, scene_filepath);
118 if (!node) {
119 goto cleanup;
120 }
121 Anima* anima = gfx_get_node_anima(node);
122 gfx_play_animation(
123 anima, &(AnimationPlaySettings){.name = "Walk", .loop = true});
124
125 *pp_state = state;
126 return true;
127
128cleanup:
129 shutdown(game, state);
130 if (state) {
131 free(state);
132 }
133 return false;
134}
135
136void shutdown(Game* game, State* state) {
137 assert(game);
138 if (state) {
139 gfx_destroy_camera(&state->camera);
140 gfx_destroy_scene(&state->scene);
141 // State freed by plugin engine.
142 }
143}
144
145void update(Game* game, State* state, double t, double dt) {
146 assert(game);
147 assert(state);
148 assert(state->scene);
149 assert(state->camera);
150
151 gfx_animate_scene(state->scene, (R)t);
152
153 const vec3 orbit_point = vec3_make(0, 2, 0);
154 Camera* camera = gfx_get_camera_camera(state->camera);
155 spatial3_orbit(
156 &camera->spatial, orbit_point,
157 /*radius=*/2.5,
158 /*azimuth=*/t * 0.5, /*zenith=*/0);
159 spatial3_lookat(&camera->spatial, orbit_point);
160}
161
162/// Render the bounding boxes of all scene objects.
163static void render_bounding_boxes_rec(ImmRenderer* imm, const SceneNode* node) {
164 assert(imm);
165 assert(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_aabb3(imm, box);
175 }
176
177 // Render children's boxes.
178 for (NodeIter it = gfx_get_node_child(node); it;
179 it = gfx_get_next_child(it)) {
180 render_bounding_boxes_rec(imm, gfx_get_iter_node(it));
181 }
182}
183
184/// Render the bounding boxes of all scene objects.
185static void render_bounding_boxes(const Game* game, const State* state) {
186 assert(game);
187 assert(state);
188
189 RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
190 ImmRenderer* imm = gfx_get_imm_renderer(game->gfx);
191 assert(render_backend);
192 assert(imm);
193
194 gfx_set_blending(render_backend, true);
195 gfx_set_depth_mask(render_backend, false);
196 gfx_set_polygon_offset(render_backend, 0.5f, 0.5f);
197
198 gfx_imm_start(imm);
199 gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera));
200 gfx_imm_set_colour(imm, vec4_make(0.2, 0.2, 1.0, 0.3));
201 render_bounding_boxes_rec(imm, gfx_get_scene_root(state->scene));
202 gfx_imm_end(imm);
203
204 gfx_set_polygon_offset(render_backend, 0.0f, 0.0f);
205 gfx_set_depth_mask(render_backend, true);
206 gfx_set_blending(render_backend, false);
207}
208
209void render(const Game* game, const State* state) {
210 assert(state);
211 assert(game);
212 assert(game->gfx);
213 assert(state->scene);
214 assert(state->camera);
215
216 Renderer* renderer = gfx_get_renderer(game->gfx);
217 assert(renderer);
218
219 gfx_render_scene(
220 renderer, &(RenderSceneParams){
221 .mode = RenderDefault,
222 .scene = state->scene,
223 .camera = state->camera});
224
225 render_bounding_boxes(game, state);
226}
227
228void resize(Game* game, State* state, int width, int height) {
229 assert(game);
230 assert(state);
231
232 const R fovy = 90 * TO_RAD;
233 const R aspect = (R)width / (R)height;
234 const R near = 0.1;
235 const R far = 1000;
236 const mat4 projection = mat4_perspective(fovy, aspect, near, far);
237
238 Camera* camera = gfx_get_camera_camera(state->camera);
239 camera->projection = projection;
240}