From a48a6caefc925af93849ac2cdd5fe5ec85ae0329 Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Thu, 19 Feb 2026 23:10:16 +0100 Subject: [PATCH] make window resizable --- include/config.hpp | 10 +++-- include/renderer.hpp | 10 +++-- src/main.cpp | 100 +++++++++++++++++++++++-------------------- src/renderer.cpp | 80 +++++++++++++++++++--------------- 4 files changed, 114 insertions(+), 86 deletions(-) diff --git a/include/config.hpp b/include/config.hpp index 189e2f5..8425538 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -3,13 +3,17 @@ #include +#define VERLET_UPDATE +// #define WEB + // Window -constexpr int WIDTH = 1300; -constexpr int HEIGHT = 1100; +constexpr int INITIAL_WIDTH = 800; +constexpr int INITIAL_HEIGHT = 800; constexpr int MENU_HEIGHT = 200; // Menu -constexpr int MENU_PAD = 10; +constexpr int MENU_PAD = 5; +constexpr int BUTTON_PAD = 20; constexpr int MENU_ROWS = 3; constexpr int MENU_COLS = 3; diff --git a/include/renderer.hpp b/include/renderer.hpp index 173977d..6124376 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -58,9 +58,11 @@ public: Renderer() : camera(OrbitCamera3D(Vector3(0, 0, 0), CAMERA_DISTANCE)), mark_solutions(false), connect_solutions(false) { - render_target = LoadRenderTexture(WIDTH, HEIGHT); - klotski_target = LoadRenderTexture(WIDTH, HEIGHT); - menu_target = LoadRenderTexture(WIDTH * 2, MENU_HEIGHT); + render_target = LoadRenderTexture(GetScreenWidth() / 2.0, + GetScreenHeight() - MENU_HEIGHT); + klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0, + GetScreenHeight() - MENU_HEIGHT); + menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT); } Renderer(const Renderer ©) = delete; @@ -84,6 +86,8 @@ public: auto UpdateCamera(const MassSpringSystem &masssprings, const State ¤t) -> void; + auto UpdateTextureSizes() -> void; + auto DrawMassSprings(const MassSpringSystem &masssprings, const State ¤t) -> void; diff --git a/src/main.cpp b/src/main.cpp index 4728568..1241b2e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,5 @@ -#define VERLET_UPDATE - #include #include -#include #include #include #include @@ -13,6 +10,9 @@ #include "renderer.hpp" #include "states.hpp" +#ifndef WEB +#include +#endif auto apply_state(MassSpringSystem &mass_springs, StateGenerator generator) -> State { @@ -59,16 +59,19 @@ auto main(int argc, char *argv[]) -> int { // return 1; // } +#ifndef WEB std::cout << "OpenMP: " << omp_get_max_threads() << " threads." << std::endl; +#endif SetTraceLogLevel(LOG_ERROR); // SetTargetFPS(60); SetConfigFlags(FLAG_VSYNC_HINT); SetConfigFlags(FLAG_MSAA_4X_HINT); - // SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN); + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN); - InitWindow(WIDTH * 2, HEIGHT + MENU_HEIGHT, "MassSprings"); + InitWindow(INITIAL_WIDTH * 2, INITIAL_HEIGHT + MENU_HEIGHT, "MassSprings"); // Rendering configuration Renderer renderer; @@ -76,7 +79,7 @@ auto main(int argc, char *argv[]) -> int { // Klotski configuration int current_preset = 0; MassSpringSystem masssprings; - State board = apply_state(masssprings, generators[current_preset]); + State current_state = apply_state(masssprings, generators[current_preset]); // Game loop float frametime; @@ -94,26 +97,28 @@ auto main(int argc, char *argv[]) -> int { frametime = GetFrameTime(); // Mouse handling - float block_size; - float x_offset = 0.0; - float y_offset = 0.0; - if (board.width > board.height) { - block_size = static_cast(WIDTH) / board.width; - y_offset = (HEIGHT - block_size * board.height) / 2.0; - } else { - block_size = static_cast(HEIGHT) / board.height; - x_offset = (WIDTH - block_size * board.width) / 2.0; - } + const int board_width = GetScreenWidth() / 2.0 - 2 * BOARD_PADDING; + const int board_height = + GetScreenHeight() - MENU_HEIGHT - 2 * BOARD_PADDING; + int block_size = std::min(board_width / current_state.width, + board_height / current_state.height) - + 2 * BLOCK_PADDING; + int x_offset = + (board_width - (block_size + 2 * BLOCK_PADDING) * current_state.width) / + 2.0; + int y_offset = (board_height - + (block_size + 2 * BLOCK_PADDING) * current_state.height) / + 2.0; Vector2 m = GetMousePosition(); if (m.x < x_offset) { hov_x = 100; } else { - hov_x = (m.x - x_offset) / block_size; + hov_x = (m.x - x_offset) / (block_size + 2 * BLOCK_PADDING); } if (m.y - MENU_HEIGHT < y_offset) { hov_y = 100; } else { - hov_y = (m.y - MENU_HEIGHT - y_offset) / block_size; + hov_y = (m.y - MENU_HEIGHT - y_offset) / (block_size + 2 * BLOCK_PADDING); } if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { sel_x = hov_x; @@ -121,74 +126,76 @@ auto main(int argc, char *argv[]) -> int { } // Key handling - std::string previous_state = board.state; + std::string previous_state = current_state.state; if (IsKeyPressed(KEY_W)) { - if (board.MoveBlockAt(sel_x, sel_y, Direction::NOR)) { + if (current_state.MoveBlockAt(sel_x, sel_y, Direction::NOR)) { sel_y--; } } else if (IsKeyPressed(KEY_A)) { - if (board.MoveBlockAt(sel_x, sel_y, Direction::WES)) { + if (current_state.MoveBlockAt(sel_x, sel_y, Direction::WES)) { sel_x--; } } else if (IsKeyPressed(KEY_S)) { - if (board.MoveBlockAt(sel_x, sel_y, Direction::SOU)) { + if (current_state.MoveBlockAt(sel_x, sel_y, Direction::SOU)) { sel_y++; } } else if (IsKeyPressed(KEY_D)) { - if (board.MoveBlockAt(sel_x, sel_y, Direction::EAS)) { + if (current_state.MoveBlockAt(sel_x, sel_y, Direction::EAS)) { sel_x++; } } else if (IsKeyPressed(KEY_P)) { - std::cout << board.state << std::endl; + std::cout << current_state.state << std::endl; } else if (IsKeyPressed(KEY_N)) { current_preset = (generators.size() + current_preset - 1) % generators.size(); - board = apply_state(masssprings, generators[current_preset]); - previous_state = board.state; + current_state = apply_state(masssprings, generators[current_preset]); + previous_state = current_state.state; } else if (IsKeyPressed(KEY_M)) { current_preset = (current_preset + 1) % generators.size(); - board = apply_state(masssprings, generators[current_preset]); - previous_state = board.state; + current_state = apply_state(masssprings, generators[current_preset]); + previous_state = current_state.state; } else if (IsKeyPressed(KEY_R)) { - board = generators[current_preset](); - previous_state = board.state; + current_state = generators[current_preset](); + previous_state = current_state.state; } else if (IsKeyPressed(KEY_C)) { - solve_closure(masssprings, board); + solve_closure(masssprings, current_state); renderer.UpdateWinningStates(masssprings, win_conditions[current_preset]); } else if (IsKeyPressed(KEY_G)) { masssprings.masses.clear(); masssprings.springs.clear(); - masssprings.AddMass(MASS, Vector3Zero(), false, board.state); - previous_state = board.state; + masssprings.AddMass(MASS, Vector3Zero(), false, current_state.state); + previous_state = current_state.state; } else if (IsKeyPressed(KEY_I)) { renderer.mark_solutions = !renderer.mark_solutions; } else if (IsKeyPressed(KEY_O)) { renderer.connect_solutions = !renderer.connect_solutions; } - if (previous_state != board.state) { + if (previous_state != current_state.state) { masssprings.AddMass( MASS, Vector3(static_cast(GetRandomValue(-1000, 1000)) / 1000.0, static_cast(GetRandomValue(-1000, 1000)) / 1000.0, static_cast(GetRandomValue(-1000, 1000)) / 1000.0), - false, board.state); - masssprings.AddSpring(board.state, previous_state, SPRING_CONSTANT, - DAMPENING_CONSTANT, REST_LENGTH); - renderer.AddWinningState(board, win_conditions[current_preset]); + false, current_state.state); + masssprings.AddSpring(current_state.state, previous_state, + SPRING_CONSTANT, DAMPENING_CONSTANT, REST_LENGTH); + renderer.AddWinningState(current_state, win_conditions[current_preset]); } // Physics update std::chrono::high_resolution_clock::time_point ps = std::chrono::high_resolution_clock::now(); - masssprings.ClearForces(); - masssprings.CalculateSpringForces(); - masssprings.CalculateRepulsionForces(); + for (int i = 0; i < UPDATES_PER_FRAME; ++i) { + masssprings.ClearForces(); + masssprings.CalculateSpringForces(); + masssprings.CalculateRepulsionForces(); #ifdef VERLET_UPDATE - masssprings.VerletUpdate(frametime * SIM_SPEED); + masssprings.VerletUpdate(frametime / UPDATES_PER_FRAME * SIM_SPEED); #else - mass_springs.EulerUpdate(frametime * SIM_SPEED); + mass_springs.EulerUpdate(frametime * SIM_SPEED); #endif + } std::chrono::high_resolution_clock::time_point pe = std::chrono::high_resolution_clock::now(); physics_time_accumulator += pe - ps; @@ -196,9 +203,10 @@ auto main(int argc, char *argv[]) -> int { // Rendering std::chrono::high_resolution_clock::time_point rs = std::chrono::high_resolution_clock::now(); - renderer.UpdateCamera(masssprings, board); - renderer.DrawMassSprings(masssprings, board); - renderer.DrawKlotski(board, hov_x, hov_y, sel_x, sel_y); + renderer.UpdateCamera(masssprings, current_state); + renderer.UpdateTextureSizes(); + renderer.DrawMassSprings(masssprings, current_state); + renderer.DrawKlotski(current_state, hov_x, hov_y, sel_x, sel_y); renderer.DrawMenu(masssprings, current_preset); renderer.DrawTextures(); std::chrono::high_resolution_clock::time_point re = diff --git a/src/renderer.cpp b/src/renderer.cpp index f1337a3..40aabaa 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -10,7 +10,7 @@ auto OrbitCamera3D::Update(const Mass ¤t_mass) -> void { Vector2 mouse = GetMousePosition(); - if (mouse.x >= WIDTH && mouse.y >= MENU_HEIGHT) { + if (mouse.x >= GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT) { if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { dragging = true; last_mouse = mouse; @@ -68,7 +68,7 @@ auto OrbitCamera3D::Update(const Mass ¤t_mass) -> void { target = current_mass.position; } - if (mouse.x >= WIDTH && mouse.y >= MENU_HEIGHT) { + if (mouse.x >= GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT) { float wheel = GetMouseWheelMove(); if (IsKeyDown(KEY_LEFT_SHIFT)) { distance -= wheel * ZOOM_SPEED * ZOOM_MULTIPLIER; @@ -114,6 +114,23 @@ auto Renderer::UpdateCamera(const MassSpringSystem &masssprings, camera.Update(c); } +auto Renderer::UpdateTextureSizes() -> void { + if (!IsWindowResized()) { + return; + } + + UnloadRenderTexture(render_target); + UnloadRenderTexture(klotski_target); + UnloadRenderTexture(menu_target); + + int width = GetScreenWidth() / 2.0; + int height = GetScreenHeight() - MENU_HEIGHT; + + render_target = LoadRenderTexture(width, height); + klotski_target = LoadRenderTexture(width, height); + menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT); +} + auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings, const State ¤t) -> void { BeginTextureMode(render_target); @@ -135,6 +152,7 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings, DrawCube(mass.position, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, RED); } else if (winning_states.contains(state)) { + // TODO: Would be better to store the winning flag in the state itself if (mark_solutions) { DrawCube(mass.position, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, BLUE); @@ -153,8 +171,7 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings, // DrawSphere(camera.target, VERTEX_SIZE, ORANGE); EndMode3D(); - DrawLine(0, 0, WIDTH, 0, BLACK); - DrawLine(0, 0, 0, HEIGHT, BLACK); + DrawLine(0, 0, 0, GetScreenHeight() - MENU_HEIGHT, BLACK); EndTextureMode(); } @@ -164,26 +181,18 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, ClearBackground(RAYWHITE); // Draw Board - const int board_width = WIDTH - 2 * BOARD_PADDING; - const int board_height = HEIGHT - 2 * BOARD_PADDING; - float block_size; - float x_offset = 0.0; - float y_offset = 0.0; - if (state.width > state.height) { - block_size = - static_cast(board_width) / state.width - 2 * BLOCK_PADDING; - y_offset = (board_height - block_size * state.height - - BLOCK_PADDING * 2 * state.height) / - 2.0; - } else { - block_size = - static_cast(board_height) / state.height - 2 * BLOCK_PADDING; - x_offset = (board_width - block_size * state.width - - BLOCK_PADDING * 2 * state.width) / - 2.0; - } + const int board_width = GetScreenWidth() / 2 - 2 * BOARD_PADDING; + const int board_height = GetScreenHeight() - MENU_HEIGHT - 2 * BOARD_PADDING; + int block_size = + std::min(board_width / state.width, board_height / state.height) - + 2 * BLOCK_PADDING; + int x_offset = + (board_width - (block_size + 2 * BLOCK_PADDING) * state.width) / 2.0; + int y_offset = + (board_height - (block_size + 2 * BLOCK_PADDING) * state.height) / 2.0; - DrawRectangle(0, 0, WIDTH, HEIGHT, RAYWHITE); + DrawRectangle(0, 0, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT, + RAYWHITE); DrawRectangle(x_offset, y_offset, board_width - 2 * x_offset + 2 * BOARD_PADDING, board_height - 2 * y_offset + 2 * BOARD_PADDING, @@ -235,8 +244,8 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, } } - DrawLine(0, 0, WIDTH, 0, BLACK); - DrawLine(WIDTH - 1, 0, WIDTH - 1, HEIGHT, BLACK); + DrawLine(GetScreenWidth() / 2 - 1, 0, GetScreenWidth() / 2 - 1, + GetScreenHeight() - MENU_HEIGHT, BLACK); EndTextureMode(); } @@ -246,7 +255,7 @@ auto Renderer::DrawMenu(const MassSpringSystem &masssprings, int current_preset) ClearBackground(RAYWHITE); float btn_width = - static_cast(WIDTH * 2 - (MENU_COLS * MENU_PAD + MENU_PAD)) / + static_cast(GetScreenWidth() - (MENU_COLS * MENU_PAD + MENU_PAD)) / MENU_COLS; float btn_height = static_cast(MENU_HEIGHT - (MENU_ROWS * MENU_PAD + MENU_PAD)) / @@ -257,8 +266,8 @@ auto Renderer::DrawMenu(const MassSpringSystem &masssprings, int current_preset) int posy = MENU_PAD + y * (MENU_PAD + btn_height); DrawRectangle(posx, posy, btn_width, btn_height, Fade(color, 0.5)); DrawRectangleLines(posx, posy, btn_width, btn_height, color); - DrawText(text.data(), posx + MENU_PAD, posy + MENU_PAD, - btn_height - 2 * MENU_PAD, WHITE); + DrawText(text.data(), posx + BUTTON_PAD, posy + BUTTON_PAD, + btn_height - 2 * BUTTON_PAD, WHITE); }; draw_btn(0, 0, @@ -286,21 +295,24 @@ auto Renderer::DrawMenu(const MassSpringSystem &masssprings, int current_preset) draw_btn(2, 1, std::format("Solve Board Closure (C)"), DARKPURPLE); draw_btn(2, 2, std::format("Clear Graph (G)"), DARKPURPLE); - // DrawLine(0, menu_height - 1, width * 2, menu_height - 1, BLACK); + DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK); EndTextureMode(); } auto Renderer::DrawTextures() -> void { BeginDrawing(); DrawTextureRec(menu_target.texture, - Rectangle(0, 0, (float)(WIDTH * 2), -(float)MENU_HEIGHT), + Rectangle(0, 0, menu_target.texture.width, + -1 * menu_target.texture.height), Vector2(0, 0), WHITE); DrawTextureRec(klotski_target.texture, - Rectangle(0, 0, (float)WIDTH, -(float)HEIGHT), + Rectangle(0, 0, klotski_target.texture.width, + -1 * klotski_target.texture.height), Vector2(0, MENU_HEIGHT), WHITE); DrawTextureRec(render_target.texture, - Rectangle(0, 0, (float)WIDTH, -(float)HEIGHT), - Vector2(WIDTH, MENU_HEIGHT), WHITE); - DrawFPS(WIDTH + 10, MENU_HEIGHT + 10); + Rectangle(0, 0, render_target.texture.width, + -1 * render_target.texture.height), + Vector2(GetScreenWidth() / 2.0, MENU_HEIGHT), WHITE); + DrawFPS(GetScreenWidth() / 2 + 10, MENU_HEIGHT + 10); EndDrawing(); }