From 3cd5b0bcca694630fcb4b977ddf7be7cd1bce153 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 20 Jan 2024 15:54:54 -0800 Subject: Rename gltfview -> game. --- CMakeLists.txt | 2 +- game/CMakeLists.txt | 22 ++++ game/src/game.c | 216 ++++++++++++++++++++++++++++++++++++ game/src/game.h | 19 ++++ game/src/plugins/CMakeLists.txt | 17 +++ game/src/plugins/gltf_view.c | 196 ++++++++++++++++++++++++++++++++ game/src/plugins/plugin.h | 52 +++++++++ game/src/plugins/texture_view.c | 147 ++++++++++++++++++++++++ gltfview/CMakeLists.txt | 22 ---- gltfview/src/game.c | 216 ------------------------------------ gltfview/src/game.h | 19 ---- gltfview/src/plugins/CMakeLists.txt | 17 --- gltfview/src/plugins/gltf_view.c | 196 -------------------------------- gltfview/src/plugins/plugin.h | 52 --------- gltfview/src/plugins/texture_view.c | 147 ------------------------ 15 files changed, 670 insertions(+), 670 deletions(-) create mode 100644 game/CMakeLists.txt create mode 100644 game/src/game.c create mode 100644 game/src/game.h create mode 100644 game/src/plugins/CMakeLists.txt create mode 100644 game/src/plugins/gltf_view.c create mode 100644 game/src/plugins/plugin.h create mode 100644 game/src/plugins/texture_view.c delete mode 100644 gltfview/CMakeLists.txt delete mode 100644 gltfview/src/game.c delete mode 100644 gltfview/src/game.h delete mode 100644 gltfview/src/plugins/CMakeLists.txt delete mode 100644 gltfview/src/plugins/gltf_view.c delete mode 100644 gltfview/src/plugins/plugin.h delete mode 100644 gltfview/src/plugins/texture_view.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a0cd5a..6ec4a34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,4 +3,4 @@ cmake_minimum_required(VERSION 3.0) add_subdirectory(gfx) add_subdirectory(gfx-app) add_subdirectory(gfx-iso) -add_subdirectory(gltfview) +add_subdirectory(game) diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt new file mode 100644 index 0000000..3a88bb7 --- /dev/null +++ b/game/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.0) + +add_subdirectory(src/plugins) + +project(game) + +add_executable(game + src/game.c) + +target_include_directories(game PRIVATE + src/) + +target_link_libraries(game PRIVATE + cstring + error + gfx + gfx-app + list + log + math + mempool + plugin) diff --git a/game/src/game.c b/game/src/game.c new file mode 100644 index 0000000..64be4f3 --- /dev/null +++ b/game/src/game.c @@ -0,0 +1,216 @@ +/* + * Main game module with entry point and game loop. + * + * The game module sets up the window and GL context and defers the core game + * logic to a plugin. + */ +#define _GNU_SOURCE 200112L // For readlink() + +#include "game.h" + +#include "plugins/plugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#undef _GNU_SOURCE + +static const int WIDTH = 1350; +static const int HEIGHT = 900; +static const int MAX_FPS = 60; + +/// Initialize the game's plugin. +static bool init_plugin(Game* game) { + assert(game); + assert(game->plugin); + // Plugin state is allowed to be null, either when the plugin does not + // expose an init() or when init() does not initialize a state. + if (plugin_resolve(game->plugin, plugin_init, "init")) { + State* plugin_state = 0; + if (!plugin_call(game->plugin, plugin_init, "init", game, &plugin_state)) { + return false; + } + set_plugin_state(game->plugin, plugin_state); + } + return true; // Plugin does not need to expose an init(). +} + +/// Shutdown the game's plugin. +/// The game's plugin is allowed to be null in the call to this function. +static void shutdown_plugin(Game* game) { + assert(game); + if (game->plugin && + (plugin_resolve(game->plugin, plugin_shutdown, "shutdown"))) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call(game->plugin, plugin_shutdown, "shutdown", game, plugin_state); + set_plugin_state(game->plugin, 0); + } +} + +/// Boot the game's plugin. +static bool boot_plugin(Game* game) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_boot, "boot")) { + void* plugin_state = get_plugin_state(game->plugin); + return plugin_call(game->plugin, plugin_boot, "boot", game, plugin_state); + } + return true; // Plugin does not need to expose a boot(). +} + +/// Update the plugin's state. +static void update_plugin(Game* game, double t, double dt) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_update, "update")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call( + game->plugin, plugin_update, "update", game, plugin_state, t, dt); + } +} + +/// Plugin render. +static void render_plugin(const Game* game) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_render, "render")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call(game->plugin, plugin_render, "render", game, plugin_state); + } +} + +/// Plugin resize. +static void resize_plugin(Game* game, int width, int height) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_resize, "resize")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call( + game->plugin, plugin_resize, "resize", game, plugin_state, width, + height); + } +} + +void app_end(Game* game); + +bool app_init(const GfxAppDesc* desc, void** app_state) { + assert(desc); + + if (desc->argc <= 1) { + LOGE("Usage: %s [plugin args]", desc->argv[0]); + return false; + } + + Game* game = calloc(1, sizeof(Game)); + if (!game) { + LOGE("Failed to allocate game state"); + return false; + } + + // Syntax: game [plugin args] + // + // Here we consume the arg so that plugins receive the remainder + // args starting from 0. + game->argc = desc->argc - 1; + game->argv = desc->argv + 1; + + char exe_path_buf[NAME_MAX] = {0}; + if (readlink("/proc/self/exe", exe_path_buf, sizeof(exe_path_buf)) == -1) { + LOGE("readlink(/proc/self/exe) failed"); + goto cleanup; + } + + // Replace the last / with a null terminator to remove the exe file from the + // path. This gets the file's parent directory. + *strrchr(exe_path_buf, '/') = 0; + + const mstring exe_dir = mstring_make(exe_path_buf); + const mstring plugins_path = mstring_concat_cstr(exe_dir, "/src/plugins"); + + if (!(game->plugin_engine = new_plugin_engine( + &(PluginEngineDesc){.plugins_dir = mstring_cstr(&plugins_path)}))) { + goto cleanup; + } + + const char* plugin = desc->argv[1]; + if (!(game->plugin = load_plugin(game->plugin_engine, plugin))) { + goto cleanup; + } + + if (!(game->gfx = gfx_init())) { + goto cleanup; + } + + if (!init_plugin(game)) { + goto cleanup; + } + if (!boot_plugin(game)) { + goto cleanup; + } + + *app_state = game; + return true; + +cleanup: + LOGE("Gfx error: %s", get_error()); + app_end(game); + return false; +} + +void app_end(Game* game) { + assert(game); + shutdown_plugin(game); + if (game->gfx) { + gfx_destroy(&game->gfx); + } + if (game->plugin) { + delete_plugin(&game->plugin); + } + if (game->plugin_engine) { + delete_plugin_engine(&game->plugin_engine); + } +} + +void app_update(Game* game, double t, double dt) { + plugin_engine_update(game->plugin_engine); + if (plugin_reloaded(game->plugin)) { + shutdown_plugin(game); + const bool result = init_plugin(game); + assert(result); // TODO: handle error better. + } + + update_plugin(game, t, dt); +} + +void app_render(const Game* game) { + RenderBackend* render_backend = gfx_get_render_backend(game->gfx); + gfx_start_frame(render_backend); + render_plugin(game); + gfx_end_frame(render_backend); +} + +void app_resize(Game* game, int width, int height) { + resize_plugin(game, width, height); +} + +GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS); diff --git a/game/src/game.h b/game/src/game.h new file mode 100644 index 0000000..93a5e39 --- /dev/null +++ b/game/src/game.h @@ -0,0 +1,19 @@ +/* + * Header file defining the game state, included by plugins. + */ +#pragma once + +typedef struct PluginEngine PluginEngine; +typedef struct Plugin Plugin; +typedef struct Gfx Gfx; +typedef struct Scene Scene; +typedef struct SceneCamera SceneCamera; + +/// Game state. +typedef struct { + int argc; + const char** argv; + PluginEngine* plugin_engine; + Plugin* plugin; + Gfx* gfx; +} Game; diff --git a/game/src/plugins/CMakeLists.txt b/game/src/plugins/CMakeLists.txt new file mode 100644 index 0000000..ecb2a45 --- /dev/null +++ b/game/src/plugins/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.0) + +project(plugins) + +set(LINK_LIBRARIES cstring math gfx) + +add_library(gltf_view SHARED + gltf_view.c) + +add_library(texture_view SHARED + texture_view.c) + +target_link_libraries(gltf_view PUBLIC + ${LINK_LIBRARIES}) + +target_link_libraries(texture_view PUBLIC + ${LINK_LIBRARIES}) diff --git a/game/src/plugins/gltf_view.c b/game/src/plugins/gltf_view.c new file mode 100644 index 0000000..c19d1b8 --- /dev/null +++ b/game/src/plugins/gltf_view.c @@ -0,0 +1,196 @@ +#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(RenderBackend* render_backend) { + return gfx_load_texture( + render_backend, + &(LoadTextureCmd){ + .origin = TextureFromFile, + .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(RenderBackend* render_backend, SceneNode* root) { + assert(render_backend); + assert(root); + + Texture* environment_map = load_environment_map(render_backend); + 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); + + SceneNode* root = gfx_get_scene_root(state->scene); + RenderBackend* render_backend = gfx_get_render_backend(game->gfx); + + Camera* camera = gfx_get_camera_camera(state->camera); + spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2)); + + SceneNode* sky_light_node = load_skyquad(render_backend, root); + if (!sky_light_node) { + return 0; + } + + SceneNode* scene_node = gfx_load_scene( + game->gfx, sky_light_node, + &(LoadSceneCmd){.origin = SceneFromFile, .filepath = 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; + } + 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) { + 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=*/2.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(ImmRenderer* imm, const SceneNode* 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? + 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_aabb(imm, box); + } + + // Render children's boxes. + for (NodeIter it = gfx_get_node_child(node); it; + it = gfx_get_next_child(it)) { + render_bounding_boxes(imm, gfx_get_iter_node(it)); + } +} + +void render(const Game* game, const State* state) { + assert(state); + assert(game); + assert(game->gfx); + assert(state->scene); + assert(state->camera); + + ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); + assert(imm); + 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(imm, gfx_get_scene_root(state->scene)); + gfx_imm_end(imm); +} diff --git a/game/src/plugins/plugin.h b/game/src/plugins/plugin.h new file mode 100644 index 0000000..a2632cd --- /dev/null +++ b/game/src/plugins/plugin.h @@ -0,0 +1,52 @@ +/* + * Game plugin. + */ +#pragma once + +#include "../game.h" + +#include +#include + +#include + +typedef struct State State; + +/// Initialize the plugin, which may optionally return a state object. +/// +/// This function is called every time the plugin is (re)loaded. +/// +/// It is assumed that the plugin's state is fully encapsulated in the returned +/// state object. The plugin should not store any (mutable) state outside of the +/// returned state object (e.g., no mutable global variables.) +bool init(Game*, State**); + +/// Shut down the plugin. +/// +/// This function is called before the plugin is unloaded. +/// +/// The plugin should perform any destruction needed, but not free the state +/// object; freeing the state object's memory is handled by the caller. +void shutdown(Game*, State*); + +/// Function called the first time the plugin is loaded throughout the +/// application's lifetime. This allows the plugin to do one-time initialization +/// of the game state. +bool boot(Game*, State*); + +/// Update the plugin's and the game's state. +void update(Game*, State*, double t, double dt); + +/// Render hook. +void render(const Game*, const State*); + +/// Called when the game's window is resized. +void resize(Game* game, State* state, int width, int height); + +// Signatures for the plugin's exposed functions. +typedef bool (*plugin_init)(Game*, State**); +typedef bool (*plugin_shutdown)(Game*, State*); +typedef bool (*plugin_boot)(Game*, State*); +typedef void (*plugin_update)(Game*, State*, double t, double dt); +typedef void (*plugin_render)(const Game*, const State*); +typedef void (*plugin_resize)(Game* game, State* state, int width, int height); diff --git a/game/src/plugins/texture_view.c b/game/src/plugins/texture_view.c new file mode 100644 index 0000000..b424158 --- /dev/null +++ b/game/src/plugins/texture_view.c @@ -0,0 +1,147 @@ +#include "plugin.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +// Default texture to load if no texture is provided. +static const char* DEFAULT_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; +// static const char* DEFAULT_TEXTURE = "/assets/checkerboard.jpg"; + +struct State { + Scene* scene; + SceneCamera* camera; +}; + +bool init(Game* game, State** pp_state) { + assert(game); + assert(pp_state); + + State* state = calloc(1, sizeof(State)); + if (!state) { + goto cleanup; + } + + // Usage: [texture file] + const char* texture_file = game->argc > 1 ? game->argv[1] : DEFAULT_TEXTURE; + + RenderBackend* render_backend = gfx_get_render_backend(game->gfx); + + Texture* texture = gfx_load_texture( + render_backend, &(LoadTextureCmd){ + .origin = TextureFromFile, + .type = LoadTexture, + .filtering = LinearFiltering, + .mipmaps = false, + .data.texture.filepath = mstring_make(texture_file)}); + if (!texture) { + goto cleanup; + } + + ShaderProgram* shader = gfx_make_view_texture_shader(render_backend); + if (!shader) { + goto cleanup; + } + + Geometry* geometry = gfx_make_quad_11(render_backend); + if (!geometry) { + goto cleanup; + } + + MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1}; + material_desc.uniforms[0] = (ShaderUniform){ + .type = UniformTexture, + .value.texture = texture, + .name = sstring_make("Texture")}; + Material* material = gfx_make_material(&material_desc); + if (!material) { + goto cleanup; + } + + const MeshDesc mesh_desc = + (MeshDesc){.geometry = geometry, .material = material, .shader = shader}; + Mesh* mesh = gfx_make_mesh(&mesh_desc); + if (!mesh) { + goto cleanup; + } + + SceneObject* object = gfx_make_object(); + if (!object) { + goto cleanup; + } + gfx_add_object_mesh(object, mesh); + + if (!(state->scene = gfx_make_scene())) { + goto cleanup; + } + + SceneNode* node = gfx_make_object_node(object); + if (!node) { + goto cleanup; + } + SceneNode* root = gfx_get_scene_root(state->scene); + if (!root) { + goto cleanup; + } + gfx_set_node_parent(node, root); + + if (!(state->camera = gfx_make_camera())) { + goto cleanup; + } + + *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. + } +} + +void render(const Game* game, const State* state) { + assert(game); + assert(state); + + Renderer* renderer = gfx_get_renderer(game->gfx); + gfx_render_scene( + renderer, &(RenderSceneParams){ + .mode = RenderDefault, + .scene = state->scene, + .camera = state->camera}); +} + +void resize(Game* game, State* state, int width, int height) { + assert(game); + assert(state); + + RenderBackend* render_backend = gfx_get_render_backend(game->gfx); + gfx_set_viewport(render_backend, width, height); + + const R fovy = 90 * 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; +} diff --git a/gltfview/CMakeLists.txt b/gltfview/CMakeLists.txt deleted file mode 100644 index 98c3b47..0000000 --- a/gltfview/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -add_subdirectory(src/plugins) - -project(gltfview) - -add_executable(gltfview - src/game.c) - -target_include_directories(gltfview PRIVATE - src/) - -target_link_libraries(gltfview PRIVATE - cstring - error - gfx - gfx-app - list - log - math - mempool - plugin) diff --git a/gltfview/src/game.c b/gltfview/src/game.c deleted file mode 100644 index 64be4f3..0000000 --- a/gltfview/src/game.c +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Main game module with entry point and game loop. - * - * The game module sets up the window and GL context and defers the core game - * logic to a plugin. - */ -#define _GNU_SOURCE 200112L // For readlink() - -#include "game.h" - -#include "plugins/plugin.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include - -#undef _GNU_SOURCE - -static const int WIDTH = 1350; -static const int HEIGHT = 900; -static const int MAX_FPS = 60; - -/// Initialize the game's plugin. -static bool init_plugin(Game* game) { - assert(game); - assert(game->plugin); - // Plugin state is allowed to be null, either when the plugin does not - // expose an init() or when init() does not initialize a state. - if (plugin_resolve(game->plugin, plugin_init, "init")) { - State* plugin_state = 0; - if (!plugin_call(game->plugin, plugin_init, "init", game, &plugin_state)) { - return false; - } - set_plugin_state(game->plugin, plugin_state); - } - return true; // Plugin does not need to expose an init(). -} - -/// Shutdown the game's plugin. -/// The game's plugin is allowed to be null in the call to this function. -static void shutdown_plugin(Game* game) { - assert(game); - if (game->plugin && - (plugin_resolve(game->plugin, plugin_shutdown, "shutdown"))) { - void* plugin_state = get_plugin_state(game->plugin); - plugin_call(game->plugin, plugin_shutdown, "shutdown", game, plugin_state); - set_plugin_state(game->plugin, 0); - } -} - -/// Boot the game's plugin. -static bool boot_plugin(Game* game) { - assert(game); - assert(game->plugin); - if (plugin_resolve(game->plugin, plugin_boot, "boot")) { - void* plugin_state = get_plugin_state(game->plugin); - return plugin_call(game->plugin, plugin_boot, "boot", game, plugin_state); - } - return true; // Plugin does not need to expose a boot(). -} - -/// Update the plugin's state. -static void update_plugin(Game* game, double t, double dt) { - assert(game); - assert(game->plugin); - if (plugin_resolve(game->plugin, plugin_update, "update")) { - void* plugin_state = get_plugin_state(game->plugin); - plugin_call( - game->plugin, plugin_update, "update", game, plugin_state, t, dt); - } -} - -/// Plugin render. -static void render_plugin(const Game* game) { - assert(game); - assert(game->plugin); - if (plugin_resolve(game->plugin, plugin_render, "render")) { - void* plugin_state = get_plugin_state(game->plugin); - plugin_call(game->plugin, plugin_render, "render", game, plugin_state); - } -} - -/// Plugin resize. -static void resize_plugin(Game* game, int width, int height) { - assert(game); - assert(game->plugin); - if (plugin_resolve(game->plugin, plugin_resize, "resize")) { - void* plugin_state = get_plugin_state(game->plugin); - plugin_call( - game->plugin, plugin_resize, "resize", game, plugin_state, width, - height); - } -} - -void app_end(Game* game); - -bool app_init(const GfxAppDesc* desc, void** app_state) { - assert(desc); - - if (desc->argc <= 1) { - LOGE("Usage: %s [plugin args]", desc->argv[0]); - return false; - } - - Game* game = calloc(1, sizeof(Game)); - if (!game) { - LOGE("Failed to allocate game state"); - return false; - } - - // Syntax: game [plugin args] - // - // Here we consume the arg so that plugins receive the remainder - // args starting from 0. - game->argc = desc->argc - 1; - game->argv = desc->argv + 1; - - char exe_path_buf[NAME_MAX] = {0}; - if (readlink("/proc/self/exe", exe_path_buf, sizeof(exe_path_buf)) == -1) { - LOGE("readlink(/proc/self/exe) failed"); - goto cleanup; - } - - // Replace the last / with a null terminator to remove the exe file from the - // path. This gets the file's parent directory. - *strrchr(exe_path_buf, '/') = 0; - - const mstring exe_dir = mstring_make(exe_path_buf); - const mstring plugins_path = mstring_concat_cstr(exe_dir, "/src/plugins"); - - if (!(game->plugin_engine = new_plugin_engine( - &(PluginEngineDesc){.plugins_dir = mstring_cstr(&plugins_path)}))) { - goto cleanup; - } - - const char* plugin = desc->argv[1]; - if (!(game->plugin = load_plugin(game->plugin_engine, plugin))) { - goto cleanup; - } - - if (!(game->gfx = gfx_init())) { - goto cleanup; - } - - if (!init_plugin(game)) { - goto cleanup; - } - if (!boot_plugin(game)) { - goto cleanup; - } - - *app_state = game; - return true; - -cleanup: - LOGE("Gfx error: %s", get_error()); - app_end(game); - return false; -} - -void app_end(Game* game) { - assert(game); - shutdown_plugin(game); - if (game->gfx) { - gfx_destroy(&game->gfx); - } - if (game->plugin) { - delete_plugin(&game->plugin); - } - if (game->plugin_engine) { - delete_plugin_engine(&game->plugin_engine); - } -} - -void app_update(Game* game, double t, double dt) { - plugin_engine_update(game->plugin_engine); - if (plugin_reloaded(game->plugin)) { - shutdown_plugin(game); - const bool result = init_plugin(game); - assert(result); // TODO: handle error better. - } - - update_plugin(game, t, dt); -} - -void app_render(const Game* game) { - RenderBackend* render_backend = gfx_get_render_backend(game->gfx); - gfx_start_frame(render_backend); - render_plugin(game); - gfx_end_frame(render_backend); -} - -void app_resize(Game* game, int width, int height) { - resize_plugin(game, width, height); -} - -GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS); diff --git a/gltfview/src/game.h b/gltfview/src/game.h deleted file mode 100644 index 93a5e39..0000000 --- a/gltfview/src/game.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Header file defining the game state, included by plugins. - */ -#pragma once - -typedef struct PluginEngine PluginEngine; -typedef struct Plugin Plugin; -typedef struct Gfx Gfx; -typedef struct Scene Scene; -typedef struct SceneCamera SceneCamera; - -/// Game state. -typedef struct { - int argc; - const char** argv; - PluginEngine* plugin_engine; - Plugin* plugin; - Gfx* gfx; -} Game; diff --git a/gltfview/src/plugins/CMakeLists.txt b/gltfview/src/plugins/CMakeLists.txt deleted file mode 100644 index ecb2a45..0000000 --- a/gltfview/src/plugins/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -project(plugins) - -set(LINK_LIBRARIES cstring math gfx) - -add_library(gltf_view SHARED - gltf_view.c) - -add_library(texture_view SHARED - texture_view.c) - -target_link_libraries(gltf_view PUBLIC - ${LINK_LIBRARIES}) - -target_link_libraries(texture_view PUBLIC - ${LINK_LIBRARIES}) diff --git a/gltfview/src/plugins/gltf_view.c b/gltfview/src/plugins/gltf_view.c deleted file mode 100644 index c19d1b8..0000000 --- a/gltfview/src/plugins/gltf_view.c +++ /dev/null @@ -1,196 +0,0 @@ -#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(RenderBackend* render_backend) { - return gfx_load_texture( - render_backend, - &(LoadTextureCmd){ - .origin = TextureFromFile, - .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(RenderBackend* render_backend, SceneNode* root) { - assert(render_backend); - assert(root); - - Texture* environment_map = load_environment_map(render_backend); - 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); - - SceneNode* root = gfx_get_scene_root(state->scene); - RenderBackend* render_backend = gfx_get_render_backend(game->gfx); - - Camera* camera = gfx_get_camera_camera(state->camera); - spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2)); - - SceneNode* sky_light_node = load_skyquad(render_backend, root); - if (!sky_light_node) { - return 0; - } - - SceneNode* scene_node = gfx_load_scene( - game->gfx, sky_light_node, - &(LoadSceneCmd){.origin = SceneFromFile, .filepath = 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; - } - 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) { - 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=*/2.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(ImmRenderer* imm, const SceneNode* 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? - 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_aabb(imm, box); - } - - // Render children's boxes. - for (NodeIter it = gfx_get_node_child(node); it; - it = gfx_get_next_child(it)) { - render_bounding_boxes(imm, gfx_get_iter_node(it)); - } -} - -void render(const Game* game, const State* state) { - assert(state); - assert(game); - assert(game->gfx); - assert(state->scene); - assert(state->camera); - - ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); - assert(imm); - 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(imm, gfx_get_scene_root(state->scene)); - gfx_imm_end(imm); -} diff --git a/gltfview/src/plugins/plugin.h b/gltfview/src/plugins/plugin.h deleted file mode 100644 index a2632cd..0000000 --- a/gltfview/src/plugins/plugin.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Game plugin. - */ -#pragma once - -#include "../game.h" - -#include -#include - -#include - -typedef struct State State; - -/// Initialize the plugin, which may optionally return a state object. -/// -/// This function is called every time the plugin is (re)loaded. -/// -/// It is assumed that the plugin's state is fully encapsulated in the returned -/// state object. The plugin should not store any (mutable) state outside of the -/// returned state object (e.g., no mutable global variables.) -bool init(Game*, State**); - -/// Shut down the plugin. -/// -/// This function is called before the plugin is unloaded. -/// -/// The plugin should perform any destruction needed, but not free the state -/// object; freeing the state object's memory is handled by the caller. -void shutdown(Game*, State*); - -/// Function called the first time the plugin is loaded throughout the -/// application's lifetime. This allows the plugin to do one-time initialization -/// of the game state. -bool boot(Game*, State*); - -/// Update the plugin's and the game's state. -void update(Game*, State*, double t, double dt); - -/// Render hook. -void render(const Game*, const State*); - -/// Called when the game's window is resized. -void resize(Game* game, State* state, int width, int height); - -// Signatures for the plugin's exposed functions. -typedef bool (*plugin_init)(Game*, State**); -typedef bool (*plugin_shutdown)(Game*, State*); -typedef bool (*plugin_boot)(Game*, State*); -typedef void (*plugin_update)(Game*, State*, double t, double dt); -typedef void (*plugin_render)(const Game*, const State*); -typedef void (*plugin_resize)(Game* game, State* state, int width, int height); diff --git a/gltfview/src/plugins/texture_view.c b/gltfview/src/plugins/texture_view.c deleted file mode 100644 index b424158..0000000 --- a/gltfview/src/plugins/texture_view.c +++ /dev/null @@ -1,147 +0,0 @@ -#include "plugin.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -// Default texture to load if no texture is provided. -static const char* DEFAULT_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; -// static const char* DEFAULT_TEXTURE = "/assets/checkerboard.jpg"; - -struct State { - Scene* scene; - SceneCamera* camera; -}; - -bool init(Game* game, State** pp_state) { - assert(game); - assert(pp_state); - - State* state = calloc(1, sizeof(State)); - if (!state) { - goto cleanup; - } - - // Usage: [texture file] - const char* texture_file = game->argc > 1 ? game->argv[1] : DEFAULT_TEXTURE; - - RenderBackend* render_backend = gfx_get_render_backend(game->gfx); - - Texture* texture = gfx_load_texture( - render_backend, &(LoadTextureCmd){ - .origin = TextureFromFile, - .type = LoadTexture, - .filtering = LinearFiltering, - .mipmaps = false, - .data.texture.filepath = mstring_make(texture_file)}); - if (!texture) { - goto cleanup; - } - - ShaderProgram* shader = gfx_make_view_texture_shader(render_backend); - if (!shader) { - goto cleanup; - } - - Geometry* geometry = gfx_make_quad_11(render_backend); - if (!geometry) { - goto cleanup; - } - - MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1}; - material_desc.uniforms[0] = (ShaderUniform){ - .type = UniformTexture, - .value.texture = texture, - .name = sstring_make("Texture")}; - Material* material = gfx_make_material(&material_desc); - if (!material) { - goto cleanup; - } - - const MeshDesc mesh_desc = - (MeshDesc){.geometry = geometry, .material = material, .shader = shader}; - Mesh* mesh = gfx_make_mesh(&mesh_desc); - if (!mesh) { - goto cleanup; - } - - SceneObject* object = gfx_make_object(); - if (!object) { - goto cleanup; - } - gfx_add_object_mesh(object, mesh); - - if (!(state->scene = gfx_make_scene())) { - goto cleanup; - } - - SceneNode* node = gfx_make_object_node(object); - if (!node) { - goto cleanup; - } - SceneNode* root = gfx_get_scene_root(state->scene); - if (!root) { - goto cleanup; - } - gfx_set_node_parent(node, root); - - if (!(state->camera = gfx_make_camera())) { - goto cleanup; - } - - *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. - } -} - -void render(const Game* game, const State* state) { - assert(game); - assert(state); - - Renderer* renderer = gfx_get_renderer(game->gfx); - gfx_render_scene( - renderer, &(RenderSceneParams){ - .mode = RenderDefault, - .scene = state->scene, - .camera = state->camera}); -} - -void resize(Game* game, State* state, int width, int height) { - assert(game); - assert(state); - - RenderBackend* render_backend = gfx_get_render_backend(game->gfx); - gfx_set_viewport(render_backend, width, height); - - const R fovy = 90 * 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; -} -- cgit v1.2.3