diff --git a/include/config.hpp b/include/config.hpp index 54f400e..1fe3807 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -5,14 +5,21 @@ // Window constexpr int WIDTH = 1300; -constexpr int HEIGHT = 1300; +constexpr int HEIGHT = 1100; +constexpr int MENU_HEIGHT = 200; + +// Menu +constexpr int MENU_PAD = 10; +constexpr int MENU_ROWS = 3; +constexpr int MENU_COLS = 3; // Camera Controls constexpr float SIM_SPEED = 4.0; -constexpr float CAMERA_DISTANCE = 250.0; +constexpr float CAMERA_DISTANCE = 100.0; constexpr float MIN_CAMERA_DISTANCE = 2.0; constexpr float MAX_CAMERA_DISTANCE = 2000.0; -constexpr float ZOOM_SPEED = 10.0; +constexpr float ZOOM_SPEED = 2.5; +constexpr float ZOOM_MULTIPLIER = 4.0; constexpr float PAN_SPEED = 2.0; constexpr float ROT_SPEED = 1.0; @@ -25,7 +32,7 @@ constexpr float REPULSION_RANGE = 5.0 * REST_LENGTH; constexpr float VERLET_DAMPENING = 0.01; // [0, 1] // Graph Drawing -constexpr float VERTEX_SIZE = 0.05; +constexpr float VERTEX_SIZE = 0.1; constexpr Color VERTEX_COLOR = GREEN; constexpr Color EDGE_COLOR = DARKGREEN; diff --git a/include/renderer.hpp b/include/renderer.hpp index 52e3cdb..7388b26 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -21,12 +21,13 @@ private: Vector2 last_mouse; bool dragging; bool panning; + bool target_lock; public: OrbitCamera3D(Vector3 target, float distance) : camera({0}), target(target), distance(distance), angle_x(0.0), angle_y(0.3), last_mouse(Vector2Zero()), dragging(false), - panning(false) { + panning(false), target_lock(true) { camera.position = Vector3(0, 0, -1.0 * distance); camera.target = target; camera.up = Vector3(0, 1.0, 0); @@ -37,23 +38,21 @@ public: ~OrbitCamera3D() {} public: - auto Update() -> void; + auto Update(const Mass ¤t_mass) -> void; }; class Renderer { private: - const int width; - const int height; OrbitCamera3D camera; RenderTexture render_target; RenderTexture klotski_target; + RenderTexture menu_target; public: - Renderer(int width, int height) - : width(width), height(height), - camera(OrbitCamera3D(Vector3(0, 0, 0), CAMERA_DISTANCE)) { - render_target = LoadRenderTexture(width, height); - klotski_target = LoadRenderTexture(width, height); + Renderer() : camera(OrbitCamera3D(Vector3(0, 0, 0), CAMERA_DISTANCE)) { + render_target = LoadRenderTexture(WIDTH, HEIGHT); + klotski_target = LoadRenderTexture(WIDTH, HEIGHT); + menu_target = LoadRenderTexture(WIDTH * 2, MENU_HEIGHT); } Renderer(const Renderer ©) = delete; @@ -64,16 +63,21 @@ public: ~Renderer() { UnloadRenderTexture(render_target); UnloadRenderTexture(klotski_target); + UnloadRenderTexture(menu_target); } public: - auto UpdateCamera() -> void; + auto UpdateCamera(const MassSpringSystem &masssprings, const State ¤t) + -> void; - auto DrawMassSprings(const MassSpringSystem &masssprings) -> void; + auto DrawMassSprings(const MassSpringSystem &masssprings, + const State ¤t) -> void; auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, int sel_y) -> void; + auto DrawMenu(const MassSpringSystem &masssprings) -> void; + auto DrawTextures() -> void; }; diff --git a/src/main.cpp b/src/main.cpp index 42283a6..6dd38f2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ -#include #define VERLET_UPDATE #include +#include #include #include #include @@ -132,10 +132,9 @@ auto state_klotski() -> State { return s; } -std::array generators{ - state_simple_1r, state_simple_1f, state_simple_2r, state_simple_2f, - state_simple_3r, state_simple_3f, state_complex_1r, state_complex_2r, - state_complex_3r, state_complex_4f, state_klotski}; +std::array generators{ + state_simple_1r, state_simple_2r, state_simple_3r, state_complex_1r, + state_complex_2r, state_complex_3r, state_complex_4f, state_klotski}; auto apply_state(MassSpringSystem &mass_springs, StateGenerator generator) -> State { @@ -145,10 +144,13 @@ auto apply_state(MassSpringSystem &mass_springs, StateGenerator generator) State s = generator(); mass_springs.AddMass(1.0, Vector3Zero(), false, s.state); - // Closure solving + return s; +}; + +auto solve_closure(MassSpringSystem &mass_springs, const State board) -> void { std::pair, std::vector>> - closure = s.Closure(); + closure = board.Closure(); for (const auto &state : closure.first) { Vector3 pos = Vector3(static_cast(GetRandomValue(-10000, 10000)) / 1000.0, @@ -171,9 +173,7 @@ auto apply_state(MassSpringSystem &mass_springs, StateGenerator generator) << sizeof(decltype(*mass_springs.springs.begin())) * mass_springs.springs.size() << " Bytes for springs." << std::endl; - - return s; -}; +} auto main(int argc, char *argv[]) -> int { // if (argc < 2) { @@ -190,15 +190,15 @@ auto main(int argc, char *argv[]) -> int { SetConfigFlags(FLAG_MSAA_4X_HINT); // SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN); - InitWindow(WIDTH * 2, HEIGHT, "MassSprings"); + InitWindow(WIDTH * 2, HEIGHT + MENU_HEIGHT, "MassSprings"); // Rendering configuration - Renderer renderer(WIDTH, HEIGHT); + Renderer renderer; // Klotski configuration int current_generator = 0; - MassSpringSystem mass_springs; - State board = apply_state(mass_springs, generators[current_generator]); + MassSpringSystem masssprings; + State board = apply_state(masssprings, generators[current_generator]); // Game loop float frametime; @@ -232,10 +232,10 @@ auto main(int argc, char *argv[]) -> int { } else { hov_x = (m.x - x_offset) / block_size; } - if (m.y < y_offset) { + if (m.y - MENU_HEIGHT < y_offset) { hov_y = 100; } else { - hov_y = (m.y - y_offset) / block_size; + hov_y = (m.y - MENU_HEIGHT - y_offset) / block_size; } if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { sel_x = hov_x; @@ -265,34 +265,42 @@ auto main(int argc, char *argv[]) -> int { } else if (IsKeyPressed(KEY_N)) { current_generator = (generators.size() + current_generator - 1) % generators.size(); - board = apply_state(mass_springs, generators[current_generator]); + board = apply_state(masssprings, generators[current_generator]); + previous_state = board.state; } else if (IsKeyPressed(KEY_M)) { current_generator = (current_generator + 1) % generators.size(); - board = apply_state(mass_springs, generators[current_generator]); + board = apply_state(masssprings, generators[current_generator]); + previous_state = board.state; } else if (IsKeyPressed(KEY_R)) { board = generators[current_generator](); + } else if (IsKeyPressed(KEY_C)) { + solve_closure(masssprings, board); + } else if (IsKeyPressed(KEY_G)) { + masssprings.masses.clear(); + masssprings.springs.clear(); + masssprings.AddMass(1.0, Vector3Zero(), false, board.state); + previous_state = board.state; } - // Don't need this as long as we're generating the closure beforehand - // if (previous_state != board.state) { - // mass_springs.AddMass( - // 1.0, - // 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); - // mass_springs.AddSpring(board.state, previous_state, SPRING_CONSTANT, - // DAMPENING_CONSTANT, REST_LENGTH); - // } + if (previous_state != board.state) { + masssprings.AddMass( + 1.0, + 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); + } // Physics update std::chrono::high_resolution_clock::time_point ps = std::chrono::high_resolution_clock::now(); - mass_springs.ClearForces(); - mass_springs.CalculateSpringForces(); - mass_springs.CalculateRepulsionForces(); + masssprings.ClearForces(); + masssprings.CalculateSpringForces(); + masssprings.CalculateRepulsionForces(); #ifdef VERLET_UPDATE - mass_springs.VerletUpdate(frametime * SIM_SPEED); + masssprings.VerletUpdate(frametime * SIM_SPEED); #else mass_springs.EulerUpdate(frametime * SIM_SPEED); #endif @@ -303,9 +311,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(); - renderer.DrawMassSprings(mass_springs); + renderer.UpdateCamera(masssprings, board); + renderer.DrawMassSprings(masssprings, board); renderer.DrawKlotski(board, hov_x, hov_y, sel_x, sel_y); + renderer.DrawMenu(masssprings); renderer.DrawTextures(); std::chrono::high_resolution_clock::time_point re = std::chrono::high_resolution_clock::now(); diff --git a/src/renderer.cpp b/src/renderer.cpp index a08f3d9..52f0b22 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,20 +1,23 @@ #include "renderer.hpp" +#include #include #include #include "config.hpp" #include "mass_springs.hpp" -auto OrbitCamera3D::Update() -> void { +auto OrbitCamera3D::Update(const Mass ¤t_mass) -> void { Vector2 mouse = GetMousePosition(); - - if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { - dragging = true; - last_mouse = mouse; - } else if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - panning = true; - last_mouse = mouse; + if (mouse.x >= WIDTH && mouse.y >= MENU_HEIGHT) { + if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { + dragging = true; + last_mouse = mouse; + } else if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + panning = true; + target_lock = false; + last_mouse = mouse; + } } if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) { @@ -24,6 +27,10 @@ auto OrbitCamera3D::Update() -> void { panning = false; } + if (IsKeyPressed(KEY_L)) { + target_lock = !target_lock; + } + if (dragging) { Vector2 dx = Vector2Subtract(mouse, last_mouse); last_mouse = mouse; @@ -50,10 +57,21 @@ auto OrbitCamera3D::Update() -> void { target = Vector3Add(target, offset); } - float wheel = GetMouseWheelMove(); - distance -= wheel * ZOOM_SPEED; + if (target_lock) { + target = current_mass.position; + } + + if (mouse.x >= WIDTH && mouse.y >= MENU_HEIGHT) { + float wheel = GetMouseWheelMove(); + if (IsKeyDown(KEY_LEFT_SHIFT)) { + distance -= wheel * ZOOM_SPEED * ZOOM_MULTIPLIER; + } else { + distance -= wheel * ZOOM_SPEED; + } + } distance = Clamp(distance, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE); + // Spherical coordinates float x = cos(angle_y) * sin(angle_x) * distance; float y = sin(angle_y) * distance; float z = cos(angle_y) * cos(angle_x) * distance; @@ -62,9 +80,14 @@ auto OrbitCamera3D::Update() -> void { camera.target = target; } -auto Renderer::UpdateCamera() -> void { camera.Update(); } +auto Renderer::UpdateCamera(const MassSpringSystem &masssprings, + const State ¤t) -> void { + const Mass &c = masssprings.masses.at(current.state); + camera.Update(c); +} -auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings) -> void { +auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings, + const State ¤t) -> void { BeginTextureMode(render_target); ClearBackground(RAYWHITE); @@ -86,12 +109,15 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings) -> void { } } - // DrawGrid(10, 1.0); + const Mass &c = masssprings.masses.at(current.state); + DrawCube(c.position, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE, RED); + // DrawGrid(10, 1.0); + // DrawSphere(camera.target, VERTEX_SIZE, ORANGE); EndMode3D(); - DrawLine(0, 0, 0, height, BLACK); - + DrawLine(0, 0, WIDTH, 0, BLACK); + DrawLine(0, 0, 0, HEIGHT, BLACK); EndTextureMode(); } @@ -101,8 +127,8 @@ 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; + 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; @@ -120,7 +146,7 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, 2.0; } - DrawRectangle(0, 0, width, height, RAYWHITE); + DrawRectangle(0, 0, WIDTH, HEIGHT, RAYWHITE); DrawRectangle(x_offset, y_offset, board_width - 2 * x_offset + 2 * BOARD_PADDING, board_height - 2 * y_offset + 2 * BOARD_PADDING, @@ -172,18 +198,67 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, } } - DrawLine(width - 1, 0, width - 1, height, BLACK); + DrawLine(0, 0, WIDTH, 0, BLACK); + DrawLine(WIDTH - 1, 0, WIDTH - 1, HEIGHT, BLACK); + EndTextureMode(); +} + +auto Renderer::DrawMenu(const MassSpringSystem &masssprings) -> void { + BeginTextureMode(menu_target); + ClearBackground(RAYWHITE); + + float btn_width = + static_cast(WIDTH * 2 - (MENU_COLS * MENU_PAD + MENU_PAD)) / + MENU_COLS; + float btn_height = + static_cast(MENU_HEIGHT - (MENU_ROWS * MENU_PAD + MENU_PAD)) / + MENU_ROWS; + + auto draw_btn = [&](int x, int y, std::string text, Color color) { + int posx = MENU_PAD + x * (MENU_PAD + btn_width); + 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); + }; + + draw_btn(0, 0, + std::format("States: {}, Transitions: {}", masssprings.masses.size(), + masssprings.springs.size()), + DARKGREEN); + draw_btn( + 0, 1, + std::format("Camera Distance (SHIFT for Fast Zoom): {}", camera.distance), + DARKGREEN); + draw_btn( + 0, 2, + std::format("Lock Camera to Current State (L): {}", camera.target_lock), + DARKGREEN); + + draw_btn(1, 0, std::format("Reset Board State (R)"), DARKBLUE); + draw_btn(1, 1, std::format("Switch to Next Preset (M)"), DARKBLUE); + draw_btn(1, 2, std::format("Switch to Previous Preset (N)"), DARKBLUE); + + draw_btn(2, 0, std::format("Print Board State to Console (P)"), DARKPURPLE); + 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); EndTextureMode(); } auto Renderer::DrawTextures() -> void { BeginDrawing(); + DrawTextureRec(menu_target.texture, + Rectangle(0, 0, (float)(WIDTH * 2), -(float)MENU_HEIGHT), + Vector2(0, 0), WHITE); DrawTextureRec(klotski_target.texture, - Rectangle(0, 0, (float)width, -(float)height), Vector2(0, 0), - WHITE); + Rectangle(0, 0, (float)WIDTH, -(float)HEIGHT), + Vector2(0, MENU_HEIGHT), WHITE); DrawTextureRec(render_target.texture, - Rectangle(0, 0, (float)width, -(float)height), - Vector2(width, 0), WHITE); - DrawFPS(width + 10, 10); + Rectangle(0, 0, (float)WIDTH, -(float)HEIGHT), + Vector2(WIDTH, MENU_HEIGHT), WHITE); + DrawFPS(WIDTH + 10, MENU_HEIGHT + 10); EndDrawing(); }