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.

---
 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 ++++++++++++++++++++++++++++++
 4 files changed, 412 insertions(+)
 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

(limited to 'game/src/plugins')

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 <gfx/renderer.h>
+#include <gfx/scene.h>
+#include <gfx/util/scene.h>
+#include <gfx/util/skyquad.h>
+#include <gfx/util/texture.h>
+#include <math/camera.h>
+#include <math/spatial3.h>
+
+#include <stdlib.h>
+
+// 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: <scene file>
+  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 <gfx/gfx.h>
+#include <gfx/scene.h>
+
+#include <stdbool.h>
+
+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 <gfx/render_backend.h>
+#include <gfx/renderer.h>
+#include <gfx/scene.h>
+#include <gfx/util/geometry.h>
+#include <gfx/util/shader.h>
+#include <gfx/util/texture.h>
+
+#include <math/camera.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+// 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