summaryrefslogtreecommitdiff
path: root/src/plugins/viewer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/viewer.c')
-rw-r--r--src/plugins/viewer.c373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/plugins/viewer.c b/src/plugins/viewer.c
new file mode 100644
index 0000000..1a27f8f
--- /dev/null
+++ b/src/plugins/viewer.c
@@ -0,0 +1,373 @@
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// Skybox.
16static const char* skybox[6] = {
17 "/home/jeanne/Nextcloud/assets/textures/skybox/clouds1/clouds1_east.bmp",
18 "/home/jeanne/Nextcloud/assets/textures/skybox/clouds1/clouds1_west.bmp",
19 "/home/jeanne/Nextcloud/assets/textures/skybox/clouds1/clouds1_up.bmp",
20 "/home/jeanne/Nextcloud/assets/textures/skybox/clouds1/clouds1_down.bmp",
21 "/home/jeanne/Nextcloud/assets/textures/skybox/clouds1/clouds1_south.bmp",
22 "/home/jeanne/Nextcloud/assets/textures/skybox/clouds1/clouds1_north.bmp",
23};
24
25// Paths to various scene files.
26static const char* BOX = "/home/jeanne/Nextcloud/assets/models/box.gltf";
27static const char* SUZANNE =
28 "/home/jeanne/Nextcloud/assets/models/suzanne.gltf";
29static const char* SPONZA = "/home/jeanne/Nextcloud/assets/glTF-Sample-Models/"
30 "2.0/Sponza/glTF/Sponza.gltf";
31static const char* FLIGHT_HELMET =
32 "/home/jeanne/Nextcloud/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/"
33 "FlightHelmet.gltf";
34static const char* DAMAGED_HELMET =
35 "/home/jeanne/Nextcloud/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/"
36 "DamagedHelmet.gltf";
37static const char* GIRL =
38 "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf";
39static const char* BOXES =
40 "/home/jeanne/Nextcloud/assets/models/boxes/boxes.gltf";
41
42#define DEFAULT_SCENE_FILE GIRL
43
44static const bool RenderBoundingBoxes = false;
45static const R DefaultCameraSpeed = (R)6.0;
46static const R DefaultMouseSensitivity = (R)(10 * TO_RAD);
47static const vec3 DefaultCameraPosition = (vec3){0, 2, 5};
48
49typedef struct CameraCommand {
50 bool CameraMoveLeft : 1;
51 bool CameraMoveRight : 1;
52 bool CameraMoveForward : 1;
53 bool CameraMoveBackward : 1;
54} CameraCommand;
55
56typedef struct CameraController {
57 R camera_speed; // Camera movement speed.
58 R mouse_sensitivity; // Controls the degree with which mouse movements
59 // rotate the camera.
60 vec2 prev_mouse_position; // Mouse position in the previous frame.
61 bool rotating; // When true, subsequent mouse movements cause the
62 // camera to rotate.
63} CameraController;
64
65typedef struct State {
66 Scene* scene;
67 Model* model;
68 SceneCamera* camera;
69 CameraController camera_controller;
70} State;
71
72/// Load the skyquad texture.
73static const Texture* load_environment_map(Gfx* gfx) {
74 assert(gfx);
75 return gfx_load_texture(
76 gfx, &(LoadTextureCmd){
77 .origin = AssetFromFile,
78 .type = LoadCubemap,
79 .colour_space = sRGB,
80 .filtering = NearestFiltering,
81 .mipmaps = false,
82 .data.cubemap.filepaths = {
83 mstring_make(skybox[0]), mstring_make(skybox[1]),
84 mstring_make(skybox[2]), mstring_make(skybox[3]),
85 mstring_make(skybox[4]), mstring_make(skybox[5])}
86 });
87}
88
89/// Load the skyquad and return the environment light node.
90static SceneNode* load_skyquad(Gfx* gfx, SceneNode* root) {
91 assert(gfx);
92 assert(root);
93
94 GfxCore* gfxcore = gfx_get_core(gfx);
95
96 const Texture* environment_map = load_environment_map(gfx);
97 if (!environment_map) {
98 return 0;
99 }
100
101 return gfx_setup_skyquad(gfxcore, root, environment_map);
102}
103
104/// Load the model.
105static Model* load_model(Game* game, State* state, const char* scene_filepath) {
106 assert(game);
107 assert(game->gfx);
108 assert(state);
109 assert(state->scene);
110
111 Camera* camera = gfx_get_camera_camera(state->camera);
112 spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2));
113
114 SceneNode* root = gfx_get_scene_root(state->scene);
115 SceneNode* sky_light_node = load_skyquad(game->gfx, root);
116 if (!sky_light_node) {
117 return 0; // test
118 }
119
120 Model* model = gfx_load_model(
121 game->gfx, &(LoadModelCmd){.origin = AssetFromFile,
122 .filepath = mstring_make(scene_filepath)});
123 if (!model) {
124 return 0;
125 }
126 SceneNode* model_node = gfx_make_model_node(model);
127 if (!model_node) {
128 return 0;
129 }
130 gfx_set_node_parent(model_node, sky_light_node);
131
132 gfx_log_node_hierarchy(root);
133
134 return model;
135}
136
137bool init(Game* game, State** pp_state) {
138 assert(game);
139
140 // Usage: <scene file>
141 const char* scene_filepath =
142 game->argc > 1 ? game->argv[1] : DEFAULT_SCENE_FILE;
143
144 State* state = calloc(1, sizeof(State));
145 if (!state) {
146 goto cleanup;
147 }
148
149 if (!(state->scene = gfx_make_scene())) {
150 goto cleanup;
151 }
152 if (!(state->camera = gfx_make_camera())) {
153 goto cleanup;
154 }
155
156 state->model = load_model(game, state, scene_filepath);
157 if (!state->model) {
158 goto cleanup;
159 }
160
161 Anima* anima = gfx_get_model_anima(state->model);
162 if (anima) {
163 gfx_play_animation(
164 anima, &(AnimationPlaySettings){.name = "Walk", .loop = true});
165 // TODO: Interpolate animations.
166 /*gfx_play_animation(
167 anima,
168 &(AnimationPlaySettings){.name = "Jumping-jack-lower", .loop = true});
169 gfx_play_animation(
170 anima, &(AnimationPlaySettings){
171 .name = "Jumping-jack-arms-mid", .loop = true});*/
172 }
173
174 spatial3_set_position(
175 &gfx_get_camera_camera(state->camera)->spatial, DefaultCameraPosition);
176
177 state->camera_controller.camera_speed = DefaultCameraSpeed;
178 state->camera_controller.mouse_sensitivity = DefaultMouseSensitivity;
179
180 *pp_state = state;
181 return true;
182
183cleanup:
184 shutdown(game, state);
185 if (state) {
186 free(state);
187 }
188 return false;
189}
190
191void shutdown(Game* game, State* state) {
192 assert(game);
193 if (state) {
194 gfx_destroy_camera(&state->camera);
195 gfx_destroy_scene(&state->scene);
196 // State freed by plugin engine.
197 }
198}
199
200static void update_camera(
201 CameraController* controller, R dt, vec2 mouse_position,
202 CameraCommand command, Spatial3* camera) {
203 assert(controller);
204 assert(camera);
205
206 // Translation.
207 const R move_x = (R)(command.CameraMoveLeft ? -1 : 0) +
208 (R)(command.CameraMoveRight ? 1 : 0);
209 const R move_y = (R)(command.CameraMoveForward ? 1 : 0) +
210 (R)(command.CameraMoveBackward ? -1 : 0);
211 const vec2 translation =
212 vec2_scale(vec2_make(move_x, move_y), controller->camera_speed * dt);
213 spatial3_move_right(camera, translation.x);
214 spatial3_move_forwards(camera, translation.y);
215
216 // Rotation.
217 if (controller->rotating) {
218 const vec2 mouse_delta =
219 vec2_sub(mouse_position, controller->prev_mouse_position);
220
221 const vec2 rotation =
222 vec2_scale(mouse_delta, controller->mouse_sensitivity * dt);
223
224 spatial3_global_yaw(camera, -rotation.x);
225 spatial3_pitch(camera, -rotation.y);
226 }
227
228 // Update controller state.
229 controller->prev_mouse_position = mouse_position;
230}
231
232void update(Game* game, State* state, double t, double dt) {
233 assert(game);
234 assert(state);
235 assert(state->scene);
236 assert(state->camera);
237
238 double mouse_x, mouse_y;
239 gfx_app_get_mouse_position(&mouse_x, &mouse_y);
240 const vec2 mouse_position = {(R)mouse_x, (R)mouse_y};
241
242 const CameraCommand camera_command = (CameraCommand){
243 .CameraMoveLeft = gfx_app_is_key_pressed(KeyA),
244 .CameraMoveRight = gfx_app_is_key_pressed(KeyD),
245 .CameraMoveForward = gfx_app_is_key_pressed(KeyW),
246 .CameraMoveBackward = gfx_app_is_key_pressed(KeyS),
247 };
248
249 state->camera_controller.rotating = gfx_app_is_mouse_button_pressed(LMB);
250
251 update_camera(
252 &state->camera_controller, (R)dt, mouse_position, camera_command,
253 &gfx_get_camera_camera(state->camera)->spatial);
254
255 // const vec3 orbit_point = vec3_make(0, 2, 0);
256 // Camera* camera = gfx_get_camera_camera(state->camera);
257 // spatial3_orbit(
258 // &camera->spatial, orbit_point,
259 // /*radius=*/5,
260 // /*azimuth=*/(R)(t * 0.5), /*zenith=*/0);
261 // spatial3_lookat(&camera->spatial, orbit_point);
262
263 gfx_update(state->scene, state->camera, (R)t);
264}
265
266/// Render the bounding boxes of all scene objects.
267static void render_bounding_boxes_rec(
268 ImmRenderer* imm, const Anima* anima, const mat4* parent_model_matrix,
269 const SceneNode* node) {
270 assert(imm);
271 assert(node);
272
273 const mat4 model_matrix =
274 mat4_mul(*parent_model_matrix, gfx_get_node_transform(node));
275
276 const NodeType node_type = gfx_get_node_type(node);
277
278 if (node_type == ModelNode) {
279 const Model* model = gfx_get_node_model(node);
280 const SceneNode* root = gfx_get_model_root(model);
281 render_bounding_boxes_rec(imm, anima, &model_matrix, root);
282 } else if (node_type == AnimaNode) {
283 anima = gfx_get_node_anima(node);
284 } else if (node_type == ObjectNode) {
285 gfx_imm_set_model_matrix(imm, &model_matrix);
286
287 const SceneObject* obj = gfx_get_node_object(node);
288 const Skeleton* skeleton = gfx_get_object_skeleton(obj);
289
290 if (skeleton) { // Animated model.
291 assert(anima);
292 const size_t num_joints = gfx_get_skeleton_num_joints(skeleton);
293 for (size_t i = 0; i < num_joints; ++i) {
294 if (gfx_joint_has_box(anima, skeleton, i)) {
295 const Box box = gfx_get_joint_box(anima, skeleton, i);
296 gfx_imm_draw_box3(imm, box.vertices);
297 }
298 }
299 } else { // Static model.
300 const aabb3 box = gfx_get_object_aabb(obj);
301 gfx_imm_draw_aabb3(imm, box);
302 }
303 }
304
305 // Render children's boxes.
306 const SceneNode* child = gfx_get_node_child(node);
307 while (child) {
308 render_bounding_boxes_rec(imm, anima, &model_matrix, child);
309 child = gfx_get_node_sibling(child);
310 }
311}
312
313/// Render the bounding boxes of all scene objects.
314static void render_bounding_boxes(const Game* game, const State* state) {
315 assert(game);
316 assert(state);
317
318 GfxCore* gfxcore = gfx_get_core(game->gfx);
319 ImmRenderer* imm = gfx_get_imm_renderer(game->gfx);
320 assert(gfxcore);
321 assert(imm);
322
323 const mat4 id = mat4_id();
324 Anima* anima = 0;
325
326 gfx_set_blending(gfxcore, true);
327 gfx_set_depth_mask(gfxcore, false);
328 gfx_set_polygon_offset(gfxcore, -1.5f, -1.0f);
329
330 gfx_imm_start(imm);
331 gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera));
332 gfx_imm_set_colour(imm, vec4_make(0.3, 0.3, 0.9, 0.1));
333 render_bounding_boxes_rec(imm, anima, &id, gfx_get_scene_root(state->scene));
334 gfx_imm_end(imm);
335
336 gfx_reset_polygon_offset(gfxcore);
337 gfx_set_depth_mask(gfxcore, true);
338 gfx_set_blending(gfxcore, false);
339}
340
341void render(const Game* game, const State* state) {
342 assert(state);
343 assert(game);
344 assert(game->gfx);
345 assert(state->scene);
346 assert(state->camera);
347
348 Renderer* renderer = gfx_get_renderer(game->gfx);
349 assert(renderer);
350
351 gfx_render_scene(
352 renderer, &(RenderSceneParams){.mode = RenderDefault,
353 .scene = state->scene,
354 .camera = state->camera});
355
356 if (RenderBoundingBoxes) {
357 render_bounding_boxes(game, state);
358 }
359}
360
361void resize(Game* game, State* state, int width, int height) {
362 assert(game);
363 assert(state);
364
365 const R fovy = 60 * TO_RAD;
366 const R aspect = (R)width / (R)height;
367 const R near = 0.1;
368 const R far = 1000;
369 const mat4 projection = mat4_perspective(fovy, aspect, near, far);
370
371 Camera* camera = gfx_get_camera_camera(state->camera);
372 camera->projection = projection;
373}