diff options
Diffstat (limited to 'src/plugins/viewer.c')
-rw-r--r-- | src/plugins/viewer.c | 373 |
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. | ||
16 | static 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. | ||
26 | static const char* BOX = "/home/jeanne/Nextcloud/assets/models/box.gltf"; | ||
27 | static const char* SUZANNE = | ||
28 | "/home/jeanne/Nextcloud/assets/models/suzanne.gltf"; | ||
29 | static const char* SPONZA = "/home/jeanne/Nextcloud/assets/glTF-Sample-Models/" | ||
30 | "2.0/Sponza/glTF/Sponza.gltf"; | ||
31 | static const char* FLIGHT_HELMET = | ||
32 | "/home/jeanne/Nextcloud/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/" | ||
33 | "FlightHelmet.gltf"; | ||
34 | static const char* DAMAGED_HELMET = | ||
35 | "/home/jeanne/Nextcloud/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/" | ||
36 | "DamagedHelmet.gltf"; | ||
37 | static const char* GIRL = | ||
38 | "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf"; | ||
39 | static const char* BOXES = | ||
40 | "/home/jeanne/Nextcloud/assets/models/boxes/boxes.gltf"; | ||
41 | |||
42 | #define DEFAULT_SCENE_FILE GIRL | ||
43 | |||
44 | static const bool RenderBoundingBoxes = false; | ||
45 | static const R DefaultCameraSpeed = (R)6.0; | ||
46 | static const R DefaultMouseSensitivity = (R)(10 * TO_RAD); | ||
47 | static const vec3 DefaultCameraPosition = (vec3){0, 2, 5}; | ||
48 | |||
49 | typedef struct CameraCommand { | ||
50 | bool CameraMoveLeft : 1; | ||
51 | bool CameraMoveRight : 1; | ||
52 | bool CameraMoveForward : 1; | ||
53 | bool CameraMoveBackward : 1; | ||
54 | } CameraCommand; | ||
55 | |||
56 | typedef 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 | |||
65 | typedef struct State { | ||
66 | Scene* scene; | ||
67 | Model* model; | ||
68 | SceneCamera* camera; | ||
69 | CameraController camera_controller; | ||
70 | } State; | ||
71 | |||
72 | /// Load the skyquad texture. | ||
73 | static 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. | ||
90 | static 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. | ||
105 | static 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 | |||
137 | bool 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 | |||
183 | cleanup: | ||
184 | shutdown(game, state); | ||
185 | if (state) { | ||
186 | free(state); | ||
187 | } | ||
188 | return false; | ||
189 | } | ||
190 | |||
191 | void 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 | |||
200 | static 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 | |||
232 | void 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. | ||
267 | static 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. | ||
314 | static 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 | |||
341 | void 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 | |||
361 | void 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 | } | ||