#include "plugin.h" #include #include #include #include #include #include #include #include #include // Paths to various scene files. static const char* BOX = "/assets/models/box.gltf"; static const char* SUZANNE = "/assets/models/suzanne.gltf"; static const char* SPONZA = "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf"; static const char* FLIGHT_HELMET = "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf"; static const char* DAMAGED_HELMET = "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf"; static const char* GIRL = "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf"; static const char* BOXES = "/home/jeanne/Nextcloud/assets/models/boxes/boxes.gltf"; #define DEFAULT_SCENE_FILE GIRL static const bool RenderBoundingBoxes = false; static const R DefaultCameraSpeed = (R)6.0; static const R DefaultMouseSensitivity = (R)(10 * TO_RAD); static const vec3 DefaultCameraPosition = (vec3){0, 2, 5}; typedef struct CameraCommand { bool CameraMoveLeft : 1; bool CameraMoveRight : 1; bool CameraMoveForward : 1; bool CameraMoveBackward : 1; } CameraCommand; typedef struct CameraController { R camera_speed; // Camera movement speed. R mouse_sensitivity; // Controls the degree with which mouse movements // rotate the camera. vec2 prev_mouse_position; // Mouse position in the previous frame. bool rotating; // When true, subsequent mouse movements cause the // camera to rotate. } CameraController; typedef struct State { Scene* scene; Model* model; SceneCamera* camera; CameraController camera_controller; } State; /// Load the skyquad texture. static const Texture* load_environment_map(Gfx* gfx) { assert(gfx); return gfx_load_texture( gfx, &(LoadTextureCmd){ .origin = AssetFromFile, .type = LoadCubemap, .colour_space = sRGB, .filtering = NearestFiltering, .mipmaps = false, .data.cubemap.filepaths = { mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"), mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"), mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"), mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"), mstring_make("/assets/skybox/clouds1/clouds1_south.bmp"), mstring_make("/assets/skybox/clouds1/clouds1_north.bmp")} }); } /// Load the skyquad and return the environment light node. static SceneNode* load_skyquad(Gfx* gfx, SceneNode* root) { assert(gfx); assert(root); GfxCore* gfxcore = gfx_get_core(gfx); const Texture* environment_map = load_environment_map(gfx); if (!environment_map) { return 0; } return gfx_setup_skyquad(gfxcore, root, environment_map); } /// Load the 3D scene. /// Return the loaded model. static Model* load_scene(Game* game, State* state, const char* scene_filepath) { assert(game); assert(game->gfx); assert(state); assert(state->scene); Camera* camera = gfx_get_camera_camera(state->camera); spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2)); SceneNode* root = gfx_get_scene_root(state->scene); SceneNode* sky_light_node = load_skyquad(game->gfx, root); if (!sky_light_node) { return 0; // test } Model* model = gfx_load_model( game->gfx, &(LoadModelCmd){ .origin = AssetFromFile, .filepath = mstring_make(scene_filepath)}); if (!model) { return 0; } SceneNode* model_node = gfx_make_model_node(model); if (!model_node) { return 0; } gfx_set_node_parent(model_node, sky_light_node); gfx_log_node_hierarchy(root); return model; } bool init(Game* game, State** pp_state) { assert(game); // Usage: const char* scene_filepath = game->argc > 1 ? game->argv[1] : DEFAULT_SCENE_FILE; State* state = calloc(1, sizeof(State)); if (!state) { goto cleanup; } if (!(state->scene = gfx_make_scene())) { goto cleanup; } if (!(state->camera = gfx_make_camera())) { goto cleanup; } state->model = load_scene(game, state, scene_filepath); if (!state->model) { goto cleanup; } Anima* anima = gfx_get_model_anima(state->model); if (anima) { gfx_play_animation( anima, &(AnimationPlaySettings){.name = "Walk", .loop = true}); // TODO: Interpolate animations. /*gfx_play_animation( anima, &(AnimationPlaySettings){.name = "Jumping-jack-lower", .loop = true}); gfx_play_animation( anima, &(AnimationPlaySettings){ .name = "Jumping-jack-arms-mid", .loop = true});*/ } spatial3_set_position( &gfx_get_camera_camera(state->camera)->spatial, DefaultCameraPosition); state->camera_controller.camera_speed = DefaultCameraSpeed; state->camera_controller.mouse_sensitivity = DefaultMouseSensitivity; *pp_state = state; return true; cleanup: shutdown(game, state); if (state) { free(state); } return false; } void shutdown(Game* game, State* state) { assert(game); if (state) { gfx_destroy_camera(&state->camera); gfx_destroy_scene(&state->scene); // State freed by plugin engine. } } static void update_camera( CameraController* controller, R dt, vec2 mouse_position, CameraCommand command, Spatial3* camera) { assert(controller); assert(camera); // Translation. const R move_x = (R)(command.CameraMoveLeft ? -1 : 0) + (R)(command.CameraMoveRight ? 1 : 0); const R move_y = (R)(command.CameraMoveForward ? 1 : 0) + (R)(command.CameraMoveBackward ? -1 : 0); const vec2 translation = vec2_scale(vec2_make(move_x, move_y), controller->camera_speed * dt); spatial3_move_right(camera, translation.x); spatial3_move_forwards(camera, translation.y); // Rotation. if (controller->rotating) { const vec2 mouse_delta = vec2_sub(mouse_position, controller->prev_mouse_position); const vec2 rotation = vec2_scale(mouse_delta, controller->mouse_sensitivity * dt); spatial3_global_yaw(camera, -rotation.x); spatial3_pitch(camera, -rotation.y); } // Update controller state. controller->prev_mouse_position = mouse_position; } void update(Game* game, State* state, double t, double dt) { assert(game); assert(state); assert(state->scene); assert(state->camera); double mouse_x, mouse_y; gfx_app_get_mouse_position(&mouse_x, &mouse_y); const vec2 mouse_position = {(R)mouse_x, (R)mouse_y}; const CameraCommand camera_command = (CameraCommand){ .CameraMoveLeft = gfx_app_is_key_pressed(KeyA), .CameraMoveRight = gfx_app_is_key_pressed(KeyD), .CameraMoveForward = gfx_app_is_key_pressed(KeyW), .CameraMoveBackward = gfx_app_is_key_pressed(KeyS), }; state->camera_controller.rotating = gfx_app_is_mouse_button_pressed(LMB); update_camera( &state->camera_controller, (R)dt, mouse_position, camera_command, &gfx_get_camera_camera(state->camera)->spatial); // const vec3 orbit_point = vec3_make(0, 2, 0); // Camera* camera = gfx_get_camera_camera(state->camera); // spatial3_orbit( // &camera->spatial, orbit_point, // /*radius=*/5, // /*azimuth=*/(R)(t * 0.5), /*zenith=*/0); // spatial3_lookat(&camera->spatial, orbit_point); gfx_update(state->scene, state->camera, (R)t); } /// Render the bounding boxes of all scene objects. static void render_bounding_boxes_rec( ImmRenderer* imm, const Anima* anima, const mat4* parent_model_matrix, const SceneNode* node) { assert(imm); assert(node); const mat4 model_matrix = mat4_mul(*parent_model_matrix, gfx_get_node_transform(node)); const NodeType node_type = gfx_get_node_type(node); if (node_type == ModelNode) { const Model* model = gfx_get_node_model(node); const SceneNode* root = gfx_get_model_root(model); render_bounding_boxes_rec(imm, anima, &model_matrix, root); } else if (node_type == AnimaNode) { anima = gfx_get_node_anima(node); } else if (node_type == ObjectNode) { gfx_imm_set_model_matrix(imm, &model_matrix); const SceneObject* obj = gfx_get_node_object(node); const Skeleton* skeleton = gfx_get_object_skeleton(obj); if (skeleton) { // Animated model. assert(anima); const size_t num_joints = gfx_get_skeleton_num_joints(skeleton); for (size_t i = 0; i < num_joints; ++i) { if (gfx_joint_has_box(anima, skeleton, i)) { const Box box = gfx_get_joint_box(anima, skeleton, i); gfx_imm_draw_box3(imm, box.vertices); } } } else { // Static model. const aabb3 box = gfx_get_object_aabb(obj); gfx_imm_draw_aabb3(imm, box); } } // Render children's boxes. const SceneNode* child = gfx_get_node_child(node); while (child) { render_bounding_boxes_rec(imm, anima, &model_matrix, child); child = gfx_get_node_sibling(child); } } /// Render the bounding boxes of all scene objects. static void render_bounding_boxes(const Game* game, const State* state) { assert(game); assert(state); GfxCore* gfxcore = gfx_get_core(game->gfx); ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); assert(gfxcore); assert(imm); const mat4 id = mat4_id(); Anima* anima = 0; gfx_set_blending(gfxcore, true); gfx_set_depth_mask(gfxcore, false); gfx_set_polygon_offset(gfxcore, -1.5f, -1.0f); gfx_imm_start(imm); gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera)); gfx_imm_set_colour(imm, vec4_make(0.3, 0.3, 0.9, 0.1)); render_bounding_boxes_rec(imm, anima, &id, gfx_get_scene_root(state->scene)); gfx_imm_end(imm); gfx_reset_polygon_offset(gfxcore); gfx_set_depth_mask(gfxcore, true); gfx_set_blending(gfxcore, false); } void render(const Game* game, const State* state) { assert(state); assert(game); assert(game->gfx); assert(state->scene); assert(state->camera); Renderer* renderer = gfx_get_renderer(game->gfx); assert(renderer); gfx_render_scene( renderer, &(RenderSceneParams){ .mode = RenderDefault, .scene = state->scene, .camera = state->camera}); if (RenderBoundingBoxes) { render_bounding_boxes(game, state); } } void resize(Game* game, State* state, int width, int height) { assert(game); assert(state); const R fovy = 60 * TO_RAD; const R aspect = (R)width / (R)height; const R near = 0.1; const R far = 1000; const mat4 projection = mat4_perspective(fovy, aspect, near, far); Camera* camera = gfx_get_camera_camera(state->camera); camera->projection = projection; }