#include "plugin.h" #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"; #define DEFAULT_SCENE_FILE GIRL struct State { Scene* scene; SceneCamera* camera; }; /// Load the skyquad texture. static 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); RenderBackend* render_backend = gfx_get_render_backend(gfx); Texture* environment_map = load_environment_map(gfx); if (!environment_map) { return 0; } return gfx_setup_skyquad(render_backend, root, environment_map); } /// Load the 3D scene. static SceneNode* 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 } SceneNode* scene_node = gfx_load_scene( game->gfx, sky_light_node, &(LoadSceneCmd){ .origin = AssetFromFile, .filepath = mstring_make(scene_filepath)}); if (!scene_node) { return 0; } gfx_log_node_hierarchy(root); return scene_node; } bool init(Game* game, State** pp_state) { assert(game); 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; } const int argc = game->argc; const char** argv = game->argv; // Usage: const char* scene_filepath = argc > 1 ? argv[1] : DEFAULT_SCENE_FILE; SceneNode* node = load_scene(game, state, scene_filepath); if (!node) { goto cleanup; } if (gfx_get_node_type(node) == AnimaNode) { Anima* anima = gfx_get_node_anima(node); gfx_play_animation( anima, &(AnimationPlaySettings){.name = "Walk", .loop = true}); } *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) { // TODO: Destroying the scene here currently does not play well with asset // reloading. The issue is that we expect to mutate the scene/model during // animation. This needs to change if we want to be able to cache assets // in memory. gfx_destroy_camera(&state->camera); gfx_destroy_scene(&state->scene); // State freed by plugin engine. } } void update(Game* game, State* state, double t, double dt) { assert(game); assert(state); assert(state->scene); assert(state->camera); gfx_animate_scene(state->scene, (R)t); 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=*/t * 0.5, /*zenith=*/0); spatial3_lookat(&camera->spatial, orbit_point); } /// Render the bounding boxes of all scene objects. static void render_bounding_boxes_rec(ImmRenderer* imm, const SceneNode* node) { assert(imm); assert(node); if (gfx_get_node_type(node) == ObjectNode) { // TODO: Look at the scene log. The JointNodes are detached from the // ObjectNodes. This is why the boxes are not being transformed as expected // here. Anima needs to animate boxes? Use OOBB in addition to AABB? // // TODO: Idea: when a model is loaded, compute an OOBB per joint using the // vertices that are affected by the joint. Then transform this OOBB when // animating the skeleton. Start with AABB for simplicity. The AABB/OOBB // in the skeleton should be const. The transform AABB/OOBB is derived // on demand. Stack allocator would be best for this kind of per-frame // data. // // TODO: After computing joint AABB/OOBBs, check here whether the node has // a skeleton, and if so, render the skeleton's boxes instead of the // node's (the node's boxes are not animated, but computer from the rest // pose). const mat4 model = gfx_get_node_global_transform(node); const SceneObject* obj = gfx_get_node_object(node); const aabb3 box = gfx_calc_object_aabb(obj); gfx_imm_set_model_matrix(imm, &model); gfx_imm_draw_aabb3(imm, box); } // Render children's boxes. for (NodeIter it = gfx_get_node_child(node); it; it = gfx_get_next_child(it)) { render_bounding_boxes_rec(imm, gfx_get_iter_node(it)); } } /// Render the bounding boxes of all scene objects. static void render_bounding_boxes(const Game* game, const State* state) { assert(game); assert(state); RenderBackend* render_backend = gfx_get_render_backend(game->gfx); ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); assert(render_backend); assert(imm); gfx_set_blending(render_backend, true); gfx_set_depth_mask(render_backend, false); gfx_set_polygon_offset(render_backend, 0.5f, 0.5f); gfx_imm_start(imm); gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera)); gfx_imm_set_colour(imm, vec4_make(0.2, 0.2, 1.0, 0.3)); render_bounding_boxes_rec(imm, gfx_get_scene_root(state->scene)); gfx_imm_end(imm); gfx_set_polygon_offset(render_backend, 0.0f, 0.0f); gfx_set_depth_mask(render_backend, true); gfx_set_blending(render_backend, 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}); 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; }