From daf6262c029892212f6b9b4014887c2c37e9ef75 Mon Sep 17 00:00:00 2001
From: 3gg <3gg@shellblade.net>
Date: Sat, 31 Aug 2024 18:58:39 -0700
Subject: Handle resizing.

---
 gfx-iso/src/app.c     | 196 -------------------------------------------------
 gfx-iso/src/backend.c | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 199 insertions(+), 196 deletions(-)
 delete mode 100644 gfx-iso/src/app.c
 create mode 100644 gfx-iso/src/backend.c

(limited to 'gfx-iso/src')

diff --git a/gfx-iso/src/app.c b/gfx-iso/src/app.c
deleted file mode 100644
index 2f2241f..0000000
--- a/gfx-iso/src/app.c
+++ /dev/null
@@ -1,196 +0,0 @@
-#include <isogfx/app.h>
-#include <isogfx/isogfx.h>
-
-#include <gfx/app.h>
-#include <gfx/core.h>
-#include <gfx/gfx.h>
-#include <gfx/renderer.h>
-#include <gfx/scene.h>
-#include <gfx/util/geometry.h>
-#include <gfx/util/shader.h>
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-static const int WINDOW_WIDTH  = 1408;
-static const int WINDOW_HEIGHT = 960;
-static const int MAX_FPS       = 60;
-
-typedef struct AppState {
-  Gfx*       gfx;
-  IsoGfx*    iso;
-  IsoGfxApp* app;
-  Texture*   screen_texture;
-  Scene*     scene;
-} AppState;
-
-typedef struct GfxAppState {
-  AppState state;
-} GfxAppState;
-
-static bool init(GfxAppState* gfx_app_state, int argc, const char** argv) {
-  assert(gfx_app_state);
-  AppState* state = &gfx_app_state->state;
-
-  IsoGfxApp* app = state->app;
-
-  // Virtual screen dimensions.
-  const int scale         = app->pixel_scale == 0 ? 1 : app->pixel_scale;
-  const int screen_width  = WINDOW_WIDTH / scale;
-  const int screen_height = WINDOW_HEIGHT / scale;
-
-  if (!(state->iso = isogfx_new(&(IsoGfxDesc){
-            .screen_width = screen_width, .screen_height = screen_height}))) {
-    goto cleanup;
-  }
-
-  if (!(*app->init)(app->state, state->iso, argc, argv)) {
-    goto cleanup;
-  }
-
-  isogfx_resize(state->iso, screen_width, screen_height);
-
-  if (!(state->gfx = gfx_init())) {
-    goto cleanup;
-  }
-  GfxCore* gfxcore = gfx_get_core(state->gfx);
-
-  if (!(state->screen_texture = gfx_make_texture(
-            gfxcore, &(TextureDesc){
-                         .width     = screen_width,
-                         .height    = screen_height,
-                         .dimension = Texture2D,
-                         .format    = TextureSRGBA8,
-                         .filtering = NearestFiltering,
-                         .wrap      = ClampToEdge,
-                         .mipmaps   = false}))) {
-    goto cleanup;
-  }
-
-  ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore);
-  if (!shader) {
-    goto cleanup;
-  }
-
-  Geometry* geometry = gfx_make_quad_11(gfxcore);
-  if (!geometry) {
-    goto cleanup;
-  }
-
-  MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1};
-  material_desc.uniforms[0]  = (ShaderUniform){
-       .type          = UniformTexture,
-       .value.texture = state->screen_texture,
-       .name          = sstring_make("Texture")};
-  Material* material = gfx_make_material(&material_desc);
-  if (!material) {
-    return false;
-  }
-
-  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(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}});
-  if (!object) {
-    goto cleanup;
-  }
-
-  state->scene    = gfx_make_scene();
-  SceneNode* node = gfx_make_object_node(object);
-  SceneNode* root = gfx_get_scene_root(state->scene);
-  gfx_set_node_parent(node, root);
-
-  return true;
-
-cleanup:
-  if (state->gfx) {
-    gfx_destroy(&state->gfx);
-  }
-  free(state);
-  return false;
-}
-
-static void shutdown(GfxAppState* gfx_app_state) {
-  assert(gfx_app_state);
-  AppState* state = &gfx_app_state->state;
-
-  if (state->app) {
-    assert(state->iso);
-    (*state->app->shutdown)(state->app->state, state->iso);
-  }
-
-  isogfx_del(&state->iso);
-  gfx_destroy(&state->gfx);
-}
-
-static void update(GfxAppState* gfx_app_state, double t, double dt) {
-  assert(gfx_app_state);
-  AppState* state = &gfx_app_state->state;
-
-  isogfx_update(state->iso, t);
-
-  assert(state->app->update);
-  (*state->app->update)(state->app->state, state->iso, t, dt);
-}
-
-static void render(GfxAppState* gfx_app_state) {
-  assert(gfx_app_state);
-  AppState* state = &gfx_app_state->state;
-
-  assert(state->app->render);
-  (*state->app->render)(state->app->state, state->iso);
-
-  const Pixel* screen = isogfx_get_screen_buffer(state->iso);
-  assert(screen);
-  gfx_update_texture(
-      state->screen_texture, &(TextureDataDesc){.pixels = screen});
-
-  GfxCore*  gfxcore  = gfx_get_core(state->gfx);
-  Renderer* renderer = gfx_get_renderer(state->gfx);
-
-  // TODO: Prevent stretching of the virtual screen onto the window; preserve
-  //  aspect ratio.
-  gfx_start_frame(gfxcore);
-  gfx_render_scene(
-      renderer, &(RenderSceneParams){
-                    .mode = RenderDefault, .scene = state->scene, .camera = 0});
-  gfx_end_frame(gfxcore);
-}
-
-static void resize(GfxAppState* gfx_app_state, int width, int height) {
-  assert(gfx_app_state);
-  AppState* state = &gfx_app_state->state;
-
-  GfxCore* gfxcore = gfx_get_core(state->gfx);
-  gfx_set_viewport(gfxcore, width, height);
-}
-
-void iso_run(int argc, const char** argv, IsoGfxApp* app) {
-  GfxAppState app_state = {
-      .state = (AppState){
-                          .app = app,
-                          }
-  };
-  gfx_app_run(
-      &(GfxAppDesc){
-          .argc              = argc,
-          .argv              = argv,
-          .width             = WINDOW_WIDTH,
-          .height            = WINDOW_HEIGHT,
-          .max_fps           = MAX_FPS,
-          .update_delta_time = MAX_FPS > 0 ? 1.0 / (double)MAX_FPS : 0.0,
-          .title             = "Isometric Renderer",
-          .app_state         = &app_state},
-      &(GfxAppCallbacks){
-          .init     = init,
-          .update   = update,
-          .render   = render,
-          .resize   = resize,
-          .shutdown = shutdown});
-}
diff --git a/gfx-iso/src/backend.c b/gfx-iso/src/backend.c
new file mode 100644
index 0000000..db91647
--- /dev/null
+++ b/gfx-iso/src/backend.c
@@ -0,0 +1,199 @@
+#include <isogfx/backend.h>
+#include <isogfx/isogfx.h>
+
+#include <gfx/core.h>
+#include <gfx/gfx.h>
+#include <gfx/renderer.h>
+#include <gfx/scene.h>
+#include <gfx/util/geometry.h>
+#include <gfx/util/shader.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+typedef struct IsoBackend {
+  Gfx*   gfx;
+  Scene* scene;
+  /// The screen or "iso screen" refers to the colour buffer of the iso graphics
+  /// library. This texture is used to draw the iso screen onto the graphics
+  /// window.
+  Texture* screen_texture;
+  /// Window size.
+  int window_width;
+  int window_height;
+  /// The viewport refers to the area inside the window to which screen_texture
+  /// is drawn. It is a scaled version of the iso screen, scaled while
+  /// respecting the iso screen's aspect ratio to prevent distortion.
+  int    viewport_x, viewport_y, viewport_width, viewport_height;
+  double stretch; // Stretch factor from iso screen dimensions to viewport
+                  // dimensions.
+} IsoBackend;
+
+IsoBackend* IsoBackendInit(const IsoGfx* iso) {
+  assert(iso);
+
+  IsoBackend* backend = calloc(1, sizeof(IsoBackend));
+  if (!backend) {
+    return 0;
+  }
+
+  if (!(backend->gfx = gfx_init())) {
+    goto cleanup;
+  }
+  GfxCore* gfxcore = gfx_get_core(backend->gfx);
+
+  int screen_width, screen_height;
+  isogfx_get_screen_size(iso, &screen_width, &screen_height);
+
+  if (!(backend->screen_texture = gfx_make_texture(
+            gfxcore, &(TextureDesc){
+                         .width     = screen_width,
+                         .height    = screen_height,
+                         .dimension = Texture2D,
+                         .format    = TextureSRGBA8,
+                         .filtering = NearestFiltering,
+                         .wrap      = ClampToEdge,
+                         .mipmaps   = false}))) {
+    goto cleanup;
+  }
+
+  ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore);
+  if (!shader) {
+    goto cleanup;
+  }
+
+  Geometry* geometry = gfx_make_quad_11(gfxcore);
+  if (!geometry) {
+    goto cleanup;
+  }
+
+  MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1};
+  material_desc.uniforms[0]  = (ShaderUniform){
+       .type          = UniformTexture,
+       .value.texture = backend->screen_texture,
+       .name          = sstring_make("Texture")};
+  Material* material = gfx_make_material(&material_desc);
+  if (!material) {
+    return false;
+  }
+
+  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(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}});
+  if (!object) {
+    goto cleanup;
+  }
+
+  backend->scene  = gfx_make_scene();
+  SceneNode* node = gfx_make_object_node(object);
+  SceneNode* root = gfx_get_scene_root(backend->scene);
+  gfx_set_node_parent(node, root);
+
+  return backend;
+
+cleanup:
+  if (backend->gfx) {
+    gfx_destroy(&backend->gfx);
+  }
+  free(backend);
+  return 0;
+}
+
+void IsoBackendShutdown(IsoBackend** ppApp) {
+  assert(ppApp);
+
+  IsoBackend* app = *ppApp;
+  if (!app) {
+    return;
+  }
+
+  gfx_destroy(&app->gfx);
+}
+
+void IsoBackendResizeWindow(
+    IsoBackend* app, const IsoGfx* iso, int width, int height) {
+  assert(app);
+  assert(iso);
+
+  app->window_width  = width;
+  app->window_height = height;
+
+  // Virtual screen dimensions.
+  int screen_width, screen_height;
+  isogfx_get_screen_size(iso, &screen_width, &screen_height);
+
+  // Stretch the virtual screen onto the viewport while respecting the screen's
+  // aspect ratio to prevent distortion.
+  if (width > height) { // Wide screen.
+    app->stretch         = (double)height / (double)screen_height;
+    app->viewport_width  = (int)((double)screen_width * app->stretch);
+    app->viewport_height = height;
+    app->viewport_x      = (width - app->viewport_width) / 2;
+    app->viewport_y      = 0;
+  } else { // Tall screen.
+    app->stretch         = (double)width / (double)screen_width;
+    app->viewport_width  = width;
+    app->viewport_height = (int)((float)screen_height * app->stretch);
+    app->viewport_x      = 0;
+    app->viewport_y      = (height - app->viewport_height) / 2;
+  }
+}
+
+void IsoBackendRender(const IsoBackend* app, const IsoGfx* iso) {
+  assert(app);
+  assert(iso);
+
+  const Pixel* screen = isogfx_get_screen_buffer(iso);
+  assert(screen);
+  gfx_update_texture(app->screen_texture, &(TextureDataDesc){.pixels = screen});
+
+  GfxCore*  gfxcore  = gfx_get_core(app->gfx);
+  Renderer* renderer = gfx_get_renderer(app->gfx);
+
+  // Clear the whole window.
+  gfx_set_viewport(gfxcore, 0, 0, app->window_width, app->window_height);
+  gfx_clear(gfxcore, vec4_make(0, 0, 0, 0));
+
+  // Draw to the subregion where the virtual screen can stretch without
+  // distortion.
+  gfx_set_viewport(
+      gfxcore, app->viewport_x, app->viewport_y, app->viewport_width,
+      app->viewport_height);
+
+  // Render the iso screen.
+  gfx_start_frame(gfxcore);
+  gfx_render_scene(
+      renderer, &(RenderSceneParams){
+                    .mode = RenderDefault, .scene = app->scene, .camera = 0});
+  gfx_end_frame(gfxcore);
+}
+
+bool IsoBackendGetMousePosition(
+    const IsoBackend* app, double window_x, double window_y, double* x,
+    double* y) {
+  assert(app);
+
+  // Translate from window coordinates to the subregion where the stretched
+  // iso screen is rendered.
+  const double screen_x = window_x - app->viewport_x;
+  const double screen_y = window_y - app->viewport_y;
+
+  // Position may be out of bounds.
+  if ((0 <= screen_x) && (screen_x < app->viewport_width) && (0 <= screen_y) &&
+      (screen_y < app->viewport_height)) {
+    // Scale back from the stretched subregion to the iso screen dimensions.
+    *x = screen_x / app->stretch;
+    *y = screen_y / app->stretch;
+    return true;
+  } else {
+    *x = -1;
+    *y = -1;
+    return false;
+  }
+}
-- 
cgit v1.2.3