From a0b2bcf7048a217b42671f8deb84db13409f7d32 Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Sat, 28 Feb 2026 17:57:24 +0100 Subject: [PATCH] small refactor --- .clang-format | 56 ++ .gitignore | 1 + CMakeLists.txt | 23 +- README.md | 6 + flake.nix | 5 +- include/camera.hpp | 33 +- include/config.hpp | 32 +- include/distance.hpp | 36 +- include/gui.hpp | 184 ------- include/input.hpp | 170 +++--- include/octree.hpp | 83 ++- include/physics.hpp | 197 +++---- include/puzzle.hpp | 475 ++++++++-------- include/renderer.hpp | 137 +++-- include/state.hpp | 103 ---- include/state_manager.hpp | 152 ++++++ include/tracy.hpp | 16 - include/user_interface.hpp | 166 ++++++ include/util.hpp | 87 ++- screenshot.png | Bin 0 -> 333947 bytes shader/instancing_fragment.glsl | 7 +- shader/instancing_vertex.glsl | 10 +- src/backward.cpp | 21 +- src/camera.cpp | 59 +- src/distance.cpp | 61 +-- src/gui.cpp | 804 ---------------------------- src/input.cpp | 922 +++++++++++++++++--------------- src/main.cpp | 99 ++-- src/octree.cpp | 291 +++++----- src/physics.cpp | 637 ++++++++++++---------- src/puzzle.cpp | 861 ++++++++++++++++------------- src/renderer.cpp | 392 +++++++------- src/state.cpp | 292 ---------- src/state_manager.cpp | 485 +++++++++++++++++ src/tracy.cpp | 22 - src/user_interface.cpp | 779 +++++++++++++++++++++++++++ 36 files changed, 4144 insertions(+), 3560 deletions(-) create mode 100644 .clang-format create mode 100644 README.md delete mode 100644 include/gui.hpp delete mode 100644 include/state.hpp create mode 100644 include/state_manager.hpp delete mode 100644 include/tracy.hpp create mode 100644 include/user_interface.hpp create mode 100644 screenshot.png delete mode 100644 src/gui.cpp delete mode 100644 src/state.cpp create mode 100644 src/state_manager.cpp delete mode 100644 src/tracy.cpp create mode 100644 src/user_interface.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b3b9463 --- /dev/null +++ b/.clang-format @@ -0,0 +1,56 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: false +AlignTrailingComments: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: true + BeforeElse: false + BeforeLambdaBody: true + BeforeWhile: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBraces: Custom +ColumnLimit: 120 +IncludeCategories: + - Regex: '^<.*' + Priority: 1 + - Regex: '^".*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseBlocks: true +IndentGotoLabels: false +IndentPPDirectives: BeforeHash +IndentWidth: 4 +InsertNewlineAtEOF: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +PointerAlignment: Left +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +TabWidth: 4 +... \ No newline at end of file diff --git a/.gitignore b/.gitignore index dbb1bac..e8bcaa8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ cmake-build-release /result /.gdb_history /valgrind.log +.idea diff --git a/CMakeLists.txt b/CMakeLists.txt index 01658b1..b50b26d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,10 +19,10 @@ endif() if(NOT DEFINED DISABLE_TRACY) include(FetchContent) FetchContent_Declare(tracy - GIT_REPOSITORY https://github.com/wolfpld/tracy.git - GIT_TAG v0.11.1 - GIT_SHALLOW TRUE - GIT_PROGRESS TRUE + GIT_REPOSITORY https://github.com/wolfpld/tracy.git + GIT_TAG v0.11.1 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE ) FetchContent_MakeAvailable(tracy) option(TRACY_ENABLE "" ON) @@ -32,7 +32,7 @@ if(NOT DEFINED DISABLE_TRACY) list(APPEND FLAGS TRACY) endif() -if (WIN32) +if(WIN32) list(APPEND LIBS opengl32 gdi32 winmm) endif() @@ -57,12 +57,11 @@ set(SOURCES src/octree.cpp src/physics.cpp src/puzzle.cpp - src/state.cpp - src/input.cpp - src/tracy.cpp - src/backward.cpp src/distance.cpp - src/gui.cpp + src/state_manager.cpp + src/input.cpp + src/user_interface.cpp + src/backward.cpp ) # Main target @@ -72,7 +71,7 @@ target_link_libraries(masssprings PRIVATE ${LIBS}) target_compile_definitions(masssprings PRIVATE ${FLAGS}) # LTO -if (NOT WIN32) +if(NOT WIN32) include(CheckIPOSupported) check_ipo_supported(RESULT supported OUTPUT error) if(supported) @@ -84,4 +83,4 @@ if (NOT WIN32) else() message(STATUS "IPO / LTO not supported: <${error}>") endif() -endif() +endif() \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d4c7cb --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# MassSprings - Puzzle Board State Space Explorer + +All combinations of pieces reachable from an initial puzzle are explored, the resulting puzzle state-space is visualized as a force-directed graph. +The graph layout is calculated iteratively using a mass-spring-system with additional pairwise repulsive forces simulated using Barnes-Hut. + +![](screenshot.png) \ No newline at end of file diff --git a/flake.nix b/flake.nix index 2c0698c..6d349ed 100644 --- a/flake.nix +++ b/flake.nix @@ -108,6 +108,7 @@ rec { valgrind # gnumake cmake + ninja # pkg-config # clang-tools # compdb @@ -253,7 +254,7 @@ rec { cd cmake-build-${typeLower} echo "Running cmake" - cmake -G "Unix Makefiles" \ + cmake -G "Ninja" \ -DCMAKE_BUILD_TYPE="${type}" \ .. @@ -297,6 +298,8 @@ rec { abbr -a rungdb "${buildDebug} && gdb --tui ./cmake-build-debug/masssprings" abbr -a runtracy "tracy -a 127.0.0.1 &; ${buildRelease} && sudo -E ./cmake-build-release/masssprings" abbr -a runvalgrind "${buildDebug} && valgrind --leak-check=full --show-reachable=no --show-leak-kinds=definite,indirect,possible --track-origins=no --suppressions=valgrind.supp --log-file=valgrind.log ./cmake-build-debug/masssprings && cat valgrind.log" + + abbr -a runclion "clion ./CMakeLists.txt 2>/dev/null 1>&2 & disown;" ''; in builtins.concatStringsSep "\n" [ diff --git a/include/camera.hpp b/include/camera.hpp index 8ce15b4..4fabc2c 100644 --- a/include/camera.hpp +++ b/include/camera.hpp @@ -1,31 +1,30 @@ -#ifndef __CAMERA_HPP_ -#define __CAMERA_HPP_ +#ifndef CAMERA_HPP_ +#define CAMERA_HPP_ #include "config.hpp" #include #include -class OrbitCamera3D { +class orbit_camera +{ public: - Vector3 position = Vector3Zero(); - Vector3 target = Vector3Zero(); - float distance = CAMERA_DISTANCE; - float fov = CAMERA_FOV; - CameraProjection projection = CAMERA_PERSPECTIVE; - float angle_x = 0.0; - float angle_y = 0.0; + Vector3 position = Vector3Zero(); + Vector3 target = Vector3Zero(); + float distance = CAMERA_DISTANCE; + float fov = CAMERA_FOV; + CameraProjection projection = CAMERA_PERSPECTIVE; + float angle_x = 0.0; + float angle_y = 0.0; - Camera camera = Camera{Vector3(0, 0, -distance), target, Vector3(0, 1.0, 0), - fov, projection}; + Camera camera = Camera{Vector3(0, 0, -distance), target, Vector3(0, 1.0, 0), fov, projection}; public: - auto Rotate(Vector2 last_mouse, Vector2 mouse) -> void; + auto rotate(Vector2 last_mouse, Vector2 mouse) -> void; - auto Pan(Vector2 last_mouse, Vector2 mouse) -> void; + auto pan(Vector2 last_mouse, Vector2 mouse) -> void; - auto Update(const Vector3 ¤t_target, const Vector3 &mass_center, - bool lock, bool mass_center_lock) -> void; + auto update(const Vector3& current_target, const Vector3& mass_center, bool lock, bool mass_center_lock) -> void; }; -#endif +#endif \ No newline at end of file diff --git a/include/config.hpp b/include/config.hpp index d5614b0..29b3fe7 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -1,5 +1,5 @@ -#ifndef __CONFIG_HPP_ -#define __CONFIG_HPP_ +#ifndef CONFIG_HPP_ +#define CONFIG_HPP_ #include @@ -12,14 +12,16 @@ // Window constexpr int INITIAL_WIDTH = 600; constexpr int INITIAL_HEIGHT = 600; -constexpr int MENU_HEIGHT = 350; +constexpr int MENU_HEIGHT = 300; +constexpr int POPUP_WIDTH = 450; +constexpr int POPUP_HEIGHT = 150; // Menu constexpr int MENU_PAD = 5; constexpr int BUTTON_PAD = 12; constexpr int MENU_ROWS = 7; constexpr int MENU_COLS = 3; -constexpr const char *FONT = "fonts/SpaceMono.ttf"; +constexpr const char* FONT = "fonts/SpaceMono.ttf"; constexpr int FONT_SIZE = 26; // Camera Controls @@ -38,17 +40,17 @@ constexpr float ROT_SPEED = 1.0; constexpr float CAMERA_SMOOTH_SPEED = 15.0; // Physics Engine -constexpr float TARGET_UPS = 90; // How often to update physics +constexpr float TARGET_UPS = 90; // How often to update physics constexpr float TIMESTEP = 1.0 / TARGET_UPS; // Update interval in seconds -constexpr float SIM_SPEED = 4.0; // How large each update should be -constexpr float MASS = 1.0; // Mass spring system -constexpr float SPRING_CONSTANT = 5.0; // Mass spring system -constexpr float DAMPENING_CONSTANT = 1.0; // Mass spring system -constexpr float REST_LENGTH = 3.0; // Mass spring system -constexpr float VERLET_DAMPENING = 0.05; // [0, 1] -constexpr float BH_FORCE = 2.5; // Barnes-Hut [1.0, 3.0] -constexpr float THETA = 0.8; // Barnes-Hut [0.5, 1.0] -constexpr float SOFTENING = 0.01; // Barnes-Hut [0.01, 1.0] +constexpr float SIM_SPEED = 4.0; // How large each update should be +constexpr float MASS = 1.0; // Mass spring system +constexpr float SPRING_CONSTANT = 5.0; // Mass spring system +constexpr float DAMPENING_CONSTANT = 1.0; // Mass spring system +constexpr float REST_LENGTH = 3.0; // Mass spring system +constexpr float VERLET_DAMPENING = 0.05; // [0, 1] +constexpr float BH_FORCE = 2.5; // Barnes-Hut [1.0, 3.0] +constexpr float THETA = 0.8; // Barnes-Hut [0.5, 1.0] +constexpr float SOFTENING = 0.01; // Barnes-Hut [0.01, 1.0] // Graph Drawing constexpr Color EDGE_COLOR = DARKBLUE; @@ -70,4 +72,4 @@ constexpr Color BLOCK_COLOR = DARKBLUE; constexpr Color TARGET_BLOCK_COLOR = RED; constexpr Color WALL_COLOR = BLACK; -#endif +#endif \ No newline at end of file diff --git a/include/distance.hpp b/include/distance.hpp index 38237eb..510b44f 100644 --- a/include/distance.hpp +++ b/include/distance.hpp @@ -1,32 +1,26 @@ -#ifndef __DISTANCE_HPP_ -#define __DISTANCE_HPP_ +#ifndef DISTANCE_HPP_ +#define DISTANCE_HPP_ #include "config.hpp" #include #include -struct DistanceResult { - // distances[n] = distance from n to target - std::vector distances; +class graph_distances +{ +public: + std::vector distances; // distances[n] = distance from node n to target + std::vector parents; // parents[n] = next node on the path from node n to target + std::vector nearest_targets; // nearest_target[n] = closest target node to node n - // parents[n] = next node on the path from n to target - std::vector parents; +public: + auto clear() -> void; + [[nodiscard]] auto empty() const -> bool; - // nearest_target[n] = closest target node to n - std::vector nearest_targets; + auto calculate_distances(size_t node_count, const std::vector>& edges, + const std::vector& targets) -> void; - auto Clear() -> void; - - auto Empty() -> bool; + [[nodiscard]] auto get_shortest_path(size_t source) const -> std::vector; }; -auto CalculateDistances( - std::size_t node_count, - const std::vector> &edges, - const std::vector &targets) -> DistanceResult; - -auto GetPath(const DistanceResult &result, std::size_t source) - -> std::vector; - -#endif +#endif \ No newline at end of file diff --git a/include/gui.hpp b/include/gui.hpp deleted file mode 100644 index cad67cd..0000000 --- a/include/gui.hpp +++ /dev/null @@ -1,184 +0,0 @@ -#ifndef __GUI_HPP_ -#define __GUI_HPP_ - -#include "camera.hpp" -#include "config.hpp" -#include "input.hpp" -#include "state.hpp" - -#include -#include - -class Grid { -public: - int x; - int y; - int width; - int height; - int columns; - int rows; - const int padding; - -public: - Grid(int _x, int _y, int _width, int _height, int _columns, int _rows, - int _padding) - : x(_x), y(_y), width(_width), height(_height), columns(_columns), - rows(_rows), padding(_padding) {} - -public: - auto UpdateBounds(int _x, int _y, int _width, int _height, int _columns, - int _rows) -> void; - - auto UpdateBounds(int _x, int _y, int _width, int _height) -> void; - - auto UpdateBounds(int _x, int _y) -> void; - - auto Bounds() const -> Rectangle; - - auto Bounds(int _x, int _y, int _width, int _height) const -> Rectangle; - - auto SquareBounds() const -> Rectangle; - - auto SquareBounds(int _x, int _y, int _width, int _height) const -> Rectangle; -}; - -class Gui { - struct Style { - int border_color_normal; - int base_color_normal; - int text_color_normal; - - int border_color_focused; - int base_color_focused; - int text_color_focused; - - int border_color_pressed; - int base_color_pressed; - int text_color_pressed; - - int border_color_disabled; - int base_color_disabled; - int text_color_disabled; - }; - - struct DefaultStyle : Style { - int background_color; - int line_color; - - int text_size; - int text_spacing; - int text_line_spacing; - int text_alignment_vertical; - int text_wrap_mode; - }; - - struct ComponentStyle : Style { - int border_width; - int text_padding; - int text_alignment; - }; - -private: - InputHandler &input; - StateManager &state; - const OrbitCamera3D &camera; - - Grid menu_grid = - Grid(0, 0, GetScreenWidth(), MENU_HEIGHT, MENU_COLS, MENU_ROWS, MENU_PAD); - - Grid board_grid = Grid( - 0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT, - state.current_state.width, state.current_state.height, BOARD_PADDING); - - Grid graph_overlay_grid = - Grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD); - - bool save_window = false; - std::array preset_name = {0}; - bool help_window = false; - -public: - Gui(InputHandler &_input, StateManager &_state, const OrbitCamera3D &_camera) - : input(_input), state(_state), camera(_camera) { - Init(); - } - - Gui(const Gui ©) = delete; - Gui &operator=(const Gui ©) = delete; - Gui(Gui &&move) = delete; - Gui &operator=(Gui &&move) = delete; - -private: - auto Init() const -> void; - - auto ApplyColor(Style &style, Color color) const -> void; - - auto ApplyBlockColor(Style &style, Color color) const -> void; - - auto ApplyTextColor(Style &style, Color color) const -> void; - - auto GetDefaultStyle() const -> DefaultStyle; - - auto SetDefaultStyle(const DefaultStyle &style) const -> void; - - auto GetComponentStyle(int component) const -> ComponentStyle; - - auto SetComponentStyle(int component, const ComponentStyle &style) const - -> void; - - auto DrawButton(Rectangle bounds, const std::string &label, Color color, - bool enabled = true, int font_size = FONT_SIZE) const -> int; - - auto DrawMenuButton(int x, int y, int width, int height, - const std::string &label, Color color, - bool enabled = true, int font_size = FONT_SIZE) const - -> int; - - auto DrawToggleSlider(Rectangle bounds, const std::string &off_label, - const std::string &on_label, int *active, Color color, - bool enabled = true, int font_size = FONT_SIZE) const - -> int; - - auto DrawMenuToggleSlider(int x, int y, int width, int height, - const std::string &off_label, - const std::string &on_label, int *active, - Color color, bool enabled = true, - int font_size = FONT_SIZE) const -> int; - - auto DrawSpinner(Rectangle bounds, const std::string &label, int *value, - int min, int max, Color color, bool enabled = true, - int font_size = FONT_SIZE) const -> int; - - auto DrawMenuSpinner(int x, int y, int width, int height, - const std::string &label, int *value, int min, int max, - Color color, bool enabled = true, - int font_size = FONT_SIZE) const -> int; - - auto DrawLabel(Rectangle bounds, const std::string &text, Color color, - bool enabled = true, int font_size = FONT_SIZE) const -> int; - - auto DrawBoardBlock(int x, int y, int width, int height, Color color, - bool enabled = true) const -> bool; - - auto WindowOpen() const -> bool; - - // Different menu sections - auto DrawMenuHeader(Color color) const -> void; - auto DrawGraphInfo(Color color) const -> void; - auto DrawGraphControls(Color color) const -> void; - auto DrawCameraControls(Color color) const -> void; - auto DrawPuzzleControls(Color color) const -> void; - auto DrawEditControls(Color color) const -> void; - auto DrawMenuFooter(Color color) -> void; - -public: - auto GetBackgroundColor() const -> Color; - auto HelpPopup() -> void; - auto DrawSavePresetPopup() -> void; - auto DrawMainMenu() -> void; - auto DrawPuzzleBoard() -> void; - auto DrawGraphOverlay(int fps, int ups) -> void; - auto Update() -> void; -}; - -#endif diff --git a/include/input.hpp b/include/input.hpp index 9774b86..6ad412d 100644 --- a/include/input.hpp +++ b/include/input.hpp @@ -1,37 +1,41 @@ -#ifndef __INPUT_HPP_ -#define __INPUT_HPP_ +#ifndef INPUT_HPP_ +#define INPUT_HPP_ #include "camera.hpp" #include "config.hpp" -#include "state.hpp" +#include "state_manager.hpp" #include #include #include -class InputHandler { - struct GenericHandler { - std::function handler; +class input_handler +{ + struct generic_handler + { + std::function handler; }; - struct MouseHandler : GenericHandler { + struct mouse_handler : generic_handler + { MouseButton button; }; - struct KeyboardHandler : GenericHandler { + struct keyboard_handler : generic_handler + { KeyboardKey key; }; private: - std::vector generic_handlers; - std::vector mouse_pressed_handlers; - std::vector mouse_released_handlers; - std::vector key_pressed_handlers; - std::vector key_released_handlers; + std::vector generic_handlers; + std::vector mouse_pressed_handlers; + std::vector mouse_released_handlers; + std::vector key_pressed_handlers; + std::vector key_released_handlers; public: - StateManager &state; - OrbitCamera3D &camera; + state_manager& state; + orbit_camera& camera; bool disable = false; @@ -63,96 +67,86 @@ public: Vector2 last_mouse = Vector2Zero(); public: - InputHandler(StateManager &_state, OrbitCamera3D &_camera) - : state(_state), camera(_camera) { - InitHandlers(); + input_handler(state_manager& _state, orbit_camera& _camera) + : state(_state), camera(_camera) + { + init_handlers(); } - InputHandler(const InputHandler ©) = delete; - InputHandler &operator=(const InputHandler ©) = delete; - InputHandler(InputHandler &&move) = delete; - InputHandler &operator=(InputHandler &&move) = delete; - - ~InputHandler() {} + input_handler(const input_handler& copy) = delete; + auto operator=(const input_handler& copy) -> input_handler& = delete; + input_handler(input_handler&& move) = delete; + auto operator=(input_handler&& move) -> input_handler& = delete; private: - auto InitHandlers() -> void; + auto init_handlers() -> void; public: // Helpers - auto MouseInMenuPane() -> bool; - auto MouseInBoardPane() -> bool; - auto MouseInGraphPane() -> bool; + [[nodiscard]] auto mouse_in_menu_pane() const -> bool; + [[nodiscard]] auto mouse_in_board_pane() const -> bool; + [[nodiscard]] auto mouse_in_graph_pane() const -> bool; // Mouse actions - auto MouseHover() -> void; - auto CameraStartPan() -> void; - auto CameraPan() -> void; - auto CameraStopPan() -> void; - auto CameraStartRotate() -> void; - auto CameraRotate() -> void; - auto CameraStopRotate() -> void; - auto CameraZoom() -> void; - auto CameraFov() -> void; - auto SelectBlock() -> void; - auto StartAddBlock() -> void; - auto ClearAddBlock() -> void; - auto AddBlock() -> void; - auto RemoveBlock() -> void; - auto PlaceGoal() -> void; + auto mouse_hover() -> void; + auto camera_start_pan() -> void; + auto camera_pan() const -> void; + auto camera_stop_pan() -> void; + auto camera_start_rotate() -> void; + auto camera_rotate() const -> void; + auto camera_stop_rotate() -> void; + auto camera_zoom() const -> void; + auto camera_fov() const -> void; + auto select_block() -> void; + auto start_add_block() -> void; + auto clear_add_block() -> void; + auto add_block() -> void; + auto remove_block() -> void; + auto place_goal() const -> void; // Key actions - auto ToggleCameraLock() -> void; - auto ToggleCameraMassCenterLock() -> void; - auto ToggleCameraProjection() -> void; - auto MoveBlockNor() -> void; - auto MoveBlockWes() -> void; - auto MoveBlockSou() -> void; - auto MoveBlockEas() -> void; - auto PrintState() const -> void; - auto PreviousPreset() -> void; - auto NextPreset() -> void; - auto ResetState() -> void; - auto FillGraph() -> void; - auto ClearGraph() -> void; - auto ToggleMarkSolutions() -> void; - auto ToggleConnectSolutions() -> void; - auto ToggleMarkPath() -> void; - auto MakeOptimalMove() -> void; - auto GoToWorstState() -> void; - auto GoToNearestTarget() -> void; - auto UndoLastMove() -> void; - auto ToggleRestrictedMovement() -> void; - auto ToggleTargetBlock() -> void; - auto ToggleWallBlock() -> void; - auto RemoveBoardColumn() -> void; - auto AddBoardColumn() -> void; - auto RemoveBoardRow() -> void; - auto AddBoardRow() -> void; - auto ToggleEditing() -> void; - auto ClearGoal() -> void; + auto toggle_camera_lock() -> void; + auto toggle_camera_mass_center_lock() -> void; + auto toggle_camera_projection() const -> void; + auto move_block_nor() -> void; + auto move_block_wes() -> void; + auto move_block_sou() -> void; + auto move_block_eas() -> void; + auto print_state() const -> void; + auto load_previous_preset() -> void; + auto load_next_preset() -> void; + auto goto_starting_state() -> void; + auto populate_graph() const -> void; + auto clear_graph() const -> void; + auto toggle_mark_solutions() -> void; + auto toggle_connect_solutions() -> void; + auto toggle_mark_path() -> void; + auto goto_optimal_next_state() const -> void; + auto goto_most_distant_state() const -> void; + auto goto_closest_target_state() const -> void; + auto goto_previous_state() const -> void; + auto toggle_restricted_movement() const -> void; + auto toggle_target_block() const -> void; + auto toggle_wall_block() const -> void; + auto remove_board_column() const -> void; + auto add_board_column() const -> void; + auto remove_board_row() const -> void; + auto add_board_row() const -> void; + auto toggle_editing() -> void; + auto clear_goal() const -> void; // General - auto RegisterGenericHandler(std::function handler) - -> void; + auto register_generic_handler(const std::function& handler) -> void; - auto RegisterMousePressedHandler(MouseButton button, - std::function handler) - -> void; + auto register_mouse_pressed_handler(MouseButton button, const std::function& handler) -> void; - auto RegisterMouseReleasedHandler(MouseButton button, - std::function handler) - -> void; + auto register_mouse_released_handler(MouseButton button, const std::function& handler) -> void; - auto RegisterKeyPressedHandler(KeyboardKey key, - std::function handler) - -> void; + auto register_key_pressed_handler(KeyboardKey key, const std::function& handler) -> void; - auto RegisterKeyReleasedHandler(KeyboardKey key, - std::function handler) - -> void; + auto register_key_released_handler(KeyboardKey key, const std::function& handler) -> void; - auto HandleInput() -> void; + auto handle_input() -> void; }; -#endif +#endif \ No newline at end of file diff --git a/include/octree.hpp b/include/octree.hpp index 4cec38b..969cc45 100644 --- a/include/octree.hpp +++ b/include/octree.hpp @@ -1,54 +1,51 @@ -#ifndef __OCTREE_HPP_ -#define __OCTREE_HPP_ - -#include "config.hpp" +#ifndef OCTREE_HPP_ +#define OCTREE_HPP_ +#include #include #include #include -class OctreeNode { -public: - Vector3 mass_center; - float mass_total; - Vector3 box_min; // area start - Vector3 box_max; // area end - int children[8]; - int mass_id; - bool leaf; +class octree +{ + class node + { + public: + Vector3 mass_center = Vector3Zero(); + float mass_total = 0.0; + Vector3 box_min = Vector3Zero(); // area start + Vector3 box_max = Vector3Zero(); // area end + std::array children = {-1, -1, -1, -1, -1, -1, -1, -1}; + int mass_id = -1; + bool leaf = true; + + public: + [[nodiscard]] auto child_count() const -> int; + }; public: - OctreeNode() - : mass_center(Vector3Zero()), mass_total(0.0), - children(-1, -1, -1, -1, -1, -1, -1, -1), mass_id(-1), leaf(true) {} + static constexpr int MAX_DEPTH = 20; + + std::vector nodes; public: - auto ChildCount() const -> int; + octree() = default; + + octree(const octree& copy) = delete; + auto operator=(const octree& copy) -> octree& = delete; + octree(octree&& move) = delete; + auto operator=(octree&& move) -> octree& = delete; + +public: + auto create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int; + + [[nodiscard]] auto get_octant(int node_idx, const Vector3& pos) const -> int; + + [[nodiscard]] auto get_child_bounds(int node_idx, int octant) const -> std::pair; + + auto insert(int node_idx, int mass_id, const Vector3& pos, float mass, int depth) -> void; + + [[nodiscard]] auto calculate_force(int node_idx, const Vector3& pos) const -> Vector3; }; -class Octree { -public: - std::vector nodes; - -public: - Octree() {} - - Octree(const Octree ©) = delete; - Octree &operator=(const Octree ©) = delete; - Octree(Octree &&move) = delete; - Octree &operator=(Octree &&move) = delete; - -public: - auto CreateNode(const Vector3 &box_min, const Vector3 &box_max) -> int; - - auto GetOctant(int node_idx, const Vector3 &pos) -> int; - - auto GetChildBounds(int node_idx, int octant) -> std::pair; - - auto Insert(int node_idx, int mass_id, const Vector3 &pos, float mass) - -> void; - - auto CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3; -}; - -#endif +#endif \ No newline at end of file diff --git a/include/physics.hpp b/include/physics.hpp index 45466be..559cd5a 100644 --- a/include/physics.hpp +++ b/include/physics.hpp @@ -1,12 +1,11 @@ -#ifndef __PHYSICS_HPP_ -#define __PHYSICS_HPP_ +#ifndef PHYSICS_HPP_ +#define PHYSICS_HPP_ #include "config.hpp" #include "octree.hpp" #include #include -#include #include #include #include @@ -15,6 +14,8 @@ #include #include +#include "util.hpp" + #ifdef THREADPOOL #if defined(_WIN32) #define NOGDI // All GDI defines and routines @@ -32,127 +33,129 @@ #include #endif -class Mass { +class mass +{ public: - Vector3 position; - Vector3 previous_position; // for verlet integration - Vector3 velocity; - Vector3 force; + Vector3 position = Vector3Zero(); + Vector3 previous_position = Vector3Zero(); // for verlet integration + Vector3 velocity = Vector3Zero(); + Vector3 force = Vector3Zero(); public: - Mass(Vector3 _position) - : position(_position), previous_position(_position), - velocity(Vector3Zero()), force(Vector3Zero()) {} + mass() = delete; + + explicit mass(const Vector3 _position) + : position(_position), previous_position(_position) {} public: - auto ClearForce() -> void; - - auto CalculateVelocity(const float delta_time) -> void; - - auto CalculatePosition(const float delta_time) -> void; - - auto VerletUpdate(const float delta_time) -> void; + auto clear_force() -> void; + auto calculate_velocity(float delta_time) -> void; + auto calculate_position(float delta_time) -> void; + auto verlet_update(float delta_time) -> void; }; -class Spring { +class spring +{ public: - std::size_t a; - std::size_t b; + size_t a; + size_t b; public: - Spring(std::size_t _a, std::size_t _b) : a(_a), b(_b) {} + spring(const size_t _a, const size_t _b) + : a(_a), b(_b) {} public: - auto CalculateSpringForce(Mass &_a, Mass &_b) const -> void; + static auto calculate_spring_force(mass& _a, mass& _b) -> void; }; -class MassSpringSystem { +class mass_spring_system +{ private: -#ifdef THREADPOOL - BS::thread_pool threads; -#endif + #ifdef THREADPOOL + BS::thread_pool<> threads; + #endif public: - Octree octree; + octree tree; // This is the main ownership of all the states/masses/springs. - std::vector masses; - std::vector springs; + std::vector masses; + std::vector springs; public: - MassSpringSystem() -#ifdef THREADPOOL - : threads(std::thread::hardware_concurrency() - 1, SetThreadName) -#endif + mass_spring_system() + #ifdef THREADPOOL + : threads(std::thread::hardware_concurrency() - 1, set_thread_name) + #endif { - std::cout << std::format( - "Using Barnes-Hut + Octree repulsion force calculation.") - << std::endl; + infoln("Using Barnes-Hut + Octree repulsion force calculation."); -#ifdef THREADPOOL - std::cout << std::format("Thread-pool: {} threads.", - threads.get_thread_count()) - << std::endl; -#else - std::cout << std::format("Thread-pool: Disabled.") << std::endl; -#endif - }; + #ifdef THREADPOOL + infoln("Thread-pool: {} threads.", threads.get_thread_count()); + #else + infoln("Thread-pool: Disabled."); + #endif + } - MassSpringSystem(const MassSpringSystem ©) = delete; - MassSpringSystem &operator=(const MassSpringSystem ©) = delete; - MassSpringSystem(MassSpringSystem &move) = delete; - MassSpringSystem &operator=(MassSpringSystem &&move) = delete; + mass_spring_system(const mass_spring_system& copy) = delete; + auto operator=(const mass_spring_system& copy) -> mass_spring_system& = delete; + mass_spring_system(mass_spring_system& move) = delete; + auto operator=(mass_spring_system&& move) -> mass_spring_system& = delete; private: -#ifdef THREADPOOL - static auto SetThreadName(std::size_t idx) -> void; -#endif + #ifdef THREADPOOL + static auto set_thread_name(size_t idx) -> void; + #endif - auto BuildOctree() -> void; + auto build_octree() -> void; public: - auto AddMass() -> void; + auto clear() -> void; + auto add_mass() -> void; + auto add_spring(size_t a, size_t b) -> void; - auto AddSpring(int a, int b) -> void; + auto clear_forces() -> void; + auto calculate_spring_forces() -> void; + auto calculate_repulsion_forces() -> void; + auto verlet_update(float delta_time) -> void; - auto Clear() -> void; - - auto ClearForces() -> void; - - auto CalculateSpringForces() -> void; - - auto CalculateRepulsionForces() -> void; - - auto VerletUpdate(float delta_time) -> void; + auto center_masses() -> void; }; -class ThreadedPhysics { - struct AddMass {}; - struct AddSpring { - std::size_t a; - std::size_t b; +class threaded_physics +{ + struct add_mass {}; + + struct add_spring + { + size_t a; + size_t b; }; - struct ClearGraph {}; - using Command = std::variant; + struct clear_graph {}; - struct PhysicsState { -#ifdef TRACY + using command = std::variant; + + struct physics_state + { + #ifdef TRACY TracyLockable(std::mutex, command_mtx); -#else + #else std::mutex command_mtx; -#endif - std::queue pending_commands; + #endif + std::queue pending_commands; -#ifdef TRACY + #ifdef TRACY TracyLockable(std::mutex, data_mtx); -#else + #else std::mutex data_mtx; -#endif + #endif std::condition_variable_any data_ready_cnd; std::condition_variable_any data_consumed_cnd; Vector3 mass_center = Vector3Zero(); - unsigned int ups = 0; + int ups = 0; + size_t mass_count = 0; // For debug + size_t spring_count = 0; // For debug std::vector masses; // Read by renderer bool data_ready = false; bool data_consumed = true; @@ -164,17 +167,19 @@ private: std::thread physics; public: - PhysicsState state; + physics_state state; public: - ThreadedPhysics() : physics(PhysicsThread, std::ref(state)) {} + threaded_physics() + : physics(physics_thread, std::ref(state)) {} - ThreadedPhysics(const ThreadedPhysics ©) = delete; - ThreadedPhysics &operator=(const ThreadedPhysics ©) = delete; - ThreadedPhysics(ThreadedPhysics &&move) = delete; - ThreadedPhysics &operator=(ThreadedPhysics &&move) = delete; + threaded_physics(const threaded_physics& copy) = delete; + auto operator=(const threaded_physics& copy) -> threaded_physics& = delete; + threaded_physics(threaded_physics&& move) = delete; + auto operator=(threaded_physics&& move) -> threaded_physics& = delete; - ~ThreadedPhysics() { + ~threaded_physics() + { state.running = false; state.data_ready_cnd.notify_all(); state.data_consumed_cnd.notify_all(); @@ -182,23 +187,23 @@ public: } private: - static auto PhysicsThread(PhysicsState &state) -> void; + static auto physics_thread(physics_state& state) -> void; public: - auto AddMassCmd() -> void; + auto add_mass_cmd() -> void; - auto AddSpringCmd(std::size_t a, std::size_t b) -> void; + auto add_spring_cmd(size_t a, size_t b) -> void; - auto ClearCmd() -> void; + auto clear_cmd() -> void; - auto AddMassSpringsCmd( - std::size_t num_masses, - const std::vector> &springs) -> void; + auto add_mass_springs_cmd(size_t num_masses, const std::vector>& springs) -> void; }; // https://en.cppreference.com/w/cpp/utility/variant/visit -template struct overloads : Ts... { +template +struct overloads : Ts... +{ using Ts::operator()...; }; -#endif +#endif \ No newline at end of file diff --git a/include/puzzle.hpp b/include/puzzle.hpp index 4f2f2bd..5068d85 100644 --- a/include/puzzle.hpp +++ b/include/puzzle.hpp @@ -1,128 +1,22 @@ -#ifndef __PUZZLE_HPP_ -#define __PUZZLE_HPP_ +#ifndef PUZZLE_HPP_ +#define PUZZLE_HPP_ #include "config.hpp" +#include "util.hpp" #include #include #include #include -#include #include #include -enum Direction { - NOR = 1 << 0, - EAS = 1 << 1, - SOU = 1 << 2, - WES = 1 << 3, -}; - -// A block is represented as a 2-digit string "wh", where w is the block width -// and h the block height. -// The target block (to remove from the board) is represented as a 2-letter -// lower-case string "xy", where x is the block width and y the block height and -// width/height are represented by [abcdefghi] (~= [123456789]). -// Immovable blocks are represented as a 2-letter upper-case string "XY", where -// X is the block width and Y the block height and width/height are represented -// by [ABCDEFGHI] (~= [123456789]). -class Block { -public: - int x; - int y; - int width; - int height; - bool target; - bool immovable; - -public: - Block(int _x, int _y, int _width, int _height, bool _target, bool _immovable) - : x(_x), y(_y), width(_width), height(_height), target(_target), - immovable(_immovable) { - if (_x < 0 || _x + _width >= 10 || _y < 0 || _y + _height >= 10) { - std::cout << std::format("Block must fit in a 9x9 board!") << std::endl; - exit(1); - } - } - - Block(int _x, int _y, int _width, int _height, bool _target) - : Block(_x, _y, _width, _height, _target, false) {} - - Block(int _x, int _y, std::string block) : x(_x), y(_y) { - if (block == "..") { - this->x = 0; - this->y = 0; - width = 0; - height = 0; - target = false; - return; - } - - const std::array target_chars{'a', 'b', 'c', 'd', 'e', - 'f', 'g', 'h', 'i'}; - - target = false; - for (const char c : target_chars) { - if (block.contains(c)) { - target = true; - break; - } - } - - const std::array immovable_chars{'A', 'B', 'C', 'D', 'E', - 'F', 'G', 'H', 'I'}; - - immovable = false; - for (const char c : immovable_chars) { - if (block.contains(c)) { - immovable = true; - break; - } - } - - if (target) { - width = static_cast(block.at(0)) - static_cast('a') + 1; - height = static_cast(block.at(1)) - static_cast('a') + 1; - } else if (immovable) { - width = static_cast(block.at(0)) - static_cast('A') + 1; - height = static_cast(block.at(1)) - static_cast('A') + 1; - } else { - width = std::stoi(block.substr(0, 1)); - height = std::stoi(block.substr(1, 1)); - } - - if (_x < 0 || _x + width >= 10 || _y < 0 || _y + height >= 10) { - std::cout << std::format("Block must fit in a 9x9 board!") << std::endl; - exit(1); - } - if (block.length() != 2) { - std::cout << std::format("Block representation must have length 2!") - << std::endl; - exit(1); - } - } - - bool operator==(const Block &other) { - return x == other.x && y == other.y && width && other.width && - target == other.target && immovable == other.immovable; - } - - bool operator!=(const Block &other) { return !(*this == other); } - -public: - auto Hash() const -> std::size_t; - - static auto Invalid() -> Block; - - auto IsValid() const -> bool; - - auto ToString() const -> std::string; - - auto GetPrincipalDirs() const -> int; - - auto Covers(int xx, int yy) const -> bool; - - auto Collides(const Block &other) const -> bool; +enum direction +{ + nor = 1 << 0, + eas = 1 << 1, + sou = 1 << 2, + wes = 1 << 3, }; // A state is represented by a string "MWHXYblocks", where M is "R" @@ -133,189 +27,316 @@ public: // representation with length 5 + 3*3 * 2). The board's cells are enumerated // from top-left to bottom-right with each block's pivot being its top-left // corner. -class State { +class puzzle +{ public: - static constexpr int prefix = 5; + // A block is represented as a 2-digit string "wh", where w is the block width + // and h the block height. + // The target block (to remove from the board) is represented as a 2-letter + // lower-case string "xy", where x is the block width and y the block height and + // width/height are represented by [abcdefghi] (~= [123456789]). + // Immovable blocks are represented as a 2-letter upper-case string "XY", where + // X is the block width and Y the block height and width/height are represented + // by [ABCDEFGHI] (~= [123456789]). + class block + { + public: + int x = 0; + int y = 0; + int width = 0; + int height = 0; + bool target = false; + bool immovable = false; - int width; - int height; - int target_x; - int target_y; - bool restricted; // Only allow blocks to move in their principal direction - std::string state; + public: + block(const int _x, const int _y, const int _width, const int _height, const bool _target = false, + const bool _immovable = false) + : x(_x), y(_y), width(_width), height(_height), target(_target), immovable(_immovable) + { + if (_x < 0 || _x + _width > 9 || _y < 0 || _y + _height > 9) { + errln("Block must fit in a 9x9 board!"); + exit(1); + } + } + block(const int _x, const int _y, const std::string& b) + : x(_x), y(_y) + { + if (b.length() != 2) { + errln("Block representation must have length 2!"); + exit(1); + } + + if (b == "..") { + this->x = 0; + this->y = 0; + width = 0; + height = 0; + target = false; + return; + } + + target = false; + constexpr std::array target_chars{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'}; + for (const char c : target_chars) { + if (b.contains(c)) { + target = true; + break; + } + } + + immovable = false; + constexpr std::array immovable_chars{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'}; + for (const char c : immovable_chars) { + if (b.contains(c)) { + immovable = true; + break; + } + } + + if (target) { + width = static_cast(b.at(0)) - static_cast('a') + 1; + height = static_cast(b.at(1)) - static_cast('a') + 1; + } else if (immovable) { + width = static_cast(b.at(0)) - static_cast('A') + 1; + height = static_cast(b.at(1)) - static_cast('A') + 1; + } else { + // println("Parsing block string '{}' at ({}, {})", b, _x, _y); + width = std::stoi(b.substr(0, 1)); + height = std::stoi(b.substr(1, 1)); + } + + if (_x < 0 || _x + width > 9 || _y < 0 || _y + height > 9) { + errln("Block must fit in a 9x9 board!"); + exit(1); + } + } + + block() = delete; + + auto operator==(const block& other) const -> bool + { + return x == other.x && y == other.y && width && other.width && target == other.target && immovable == other. + immovable; + } + + auto operator!=(const block& other) const -> bool + { + return !(*this == other); + } + + public: + [[nodiscard]] auto hash() const -> size_t; + [[nodiscard]] auto valid() const -> bool; + [[nodiscard]] auto string() const -> std::string; + + // Movement + + [[nodiscard]] auto principal_dirs() const -> int; + [[nodiscard]] auto covers(int _x, int _y) const -> bool; + [[nodiscard]] auto collides(const block& b) const -> bool; + }; + +private: // https://en.cppreference.com/w/cpp/iterator/input_iterator.html - class BlockIterator { + class block_iterator + { public: using difference_type = std::ptrdiff_t; - using value_type = Block; + using value_type = block; private: - const State &state; + const puzzle& state; int current_pos; public: - BlockIterator(const State &_state) : state(_state), current_pos(0) {} + explicit block_iterator(const puzzle& _state) + : state(_state), current_pos(0) {} - BlockIterator(const State &_state, int _current_pos) - : state(_state), current_pos(_current_pos) {} + block_iterator(const puzzle& _state, const int _current_pos) + : state(_state), current_pos(_current_pos) {} - Block operator*() const { - return Block(current_pos % state.width, current_pos / state.width, - state.state.substr(current_pos * 2 + prefix, 2)); + auto operator*() const -> block + { + return {current_pos % state.width, current_pos / state.width, state.state.substr(current_pos * 2 + PREFIX, 2)}; } - BlockIterator &operator++() { + auto operator++() -> block_iterator& + { do { current_pos++; - } while (state.state.substr(current_pos * 2 + prefix, 2) == ".."); + } while (current_pos < state.width * state.height && state.state.substr(current_pos * 2 + PREFIX, 2) == ".."); return *this; } - bool operator==(const BlockIterator &other) { + auto operator==(const block_iterator& other) const -> bool + { return state == other.state && current_pos == other.current_pos; } - bool operator!=(const BlockIterator &other) { return !(*this == other); } + auto operator!=(const block_iterator& other) const -> bool + { + return !(*this == other); + } }; public: - State(int _width, int _height, int _target_x, int _target_y, bool _restricted) - : width(_width), height(_height), target_x(_target_x), - target_y(_target_y), restricted(_restricted), - state(std::format("{}{}{}{}{}{}", _restricted ? "R" : "F", _width, - _height, _target_x, _target_y, - std::string(_width * _height * 2, '.'))) { - if (_width < 1 || _width > 9 || _height < 1 || _height > 9) { - std::cout << std::format("State width/height must be in [1, 9]!") - << std::endl; + static constexpr int PREFIX = 5; + static constexpr int MIN_WIDTH = 3; + static constexpr int MIN_HEIGHT = 3; + static constexpr int MAX_WIDTH = 9; + static constexpr int MAX_HEIGHT = 9; + + int width = 0; + int height = 0; + int target_x = 9; + int target_y = 9; + bool restricted = false; // Only allow blocks to move in their principal direction + std::string state; + +public: + puzzle() = delete; + + puzzle(const int w, const int h, const int tx, const int ty, const bool r) + : width(w), height(h), target_x(tx), target_y(ty), restricted(r), + state(std::format("{}{}{}{}{}{}", r ? "R" : "F", w, h, tx, ty, std::string(w * h * 2, '.'))) + { + if (w < 1 || w > 9 || h < 1 || h > 9) { + errln("State width/height must be in [1, 9]!"); exit(1); } - if (_target_x < 0 || _target_x >= 9 || _target_y < 0 || _target_y >= 9) { - if (_target_x != 9 && _target_y != 9) { - std::cout << std::format( - "State target must be within the board bounds!") - << std::endl; + if (tx < 0 || tx >= 9 || ty < 0 || ty >= 9) { + if (tx != 9 && ty != 9) { + errln("State target must be within the board bounds!"); exit(1); } } } - State(int _width, int _height, bool _restricted) - : State(_width, _height, 9, 9, _restricted) {} + puzzle(const int w, const int h, const bool r) + : puzzle(w, h, 9, 9, r) {} - State() : State(4, 5, 9, 9, false) {} - - explicit State(std::string _state) - : width(std::stoi(_state.substr(1, 1))), - height(std::stoi(_state.substr(2, 1))), - target_x(std::stoi(_state.substr(3, 1))), - target_y(std::stoi(_state.substr(4, 1))), - restricted(_state.substr(0, 1) == "R"), state(_state) { + explicit puzzle(const std::string& s) + : width(std::stoi(s.substr(1, 1))), height(std::stoi(s.substr(2, 1))), target_x(std::stoi(s.substr(3, 1))), + target_y(std::stoi(s.substr(4, 1))), restricted(s.substr(0, 1) == "R"), state(s) + { if (width < 1 || width > 9 || height < 1 || height > 9) { - std::cout << std::format("State width/height must be in [1, 9]!") - << std::endl; + errln("State width/height must be in [1, 9]!"); exit(1); } if (target_x < 0 || target_x >= 9 || target_y < 0 || target_y >= 9) { if (target_x != 9 && target_y != 9) { - std::cout << std::format( - "State target must be within the board bounds!") - << std::endl; + errln("State target must be within the board bounds!"); exit(1); } } - if (static_cast(_state.length()) != width * height * 2 + prefix) { - std::cout << std::format("State representation must have length width * " - "height * 2 + {}!", - prefix) - << std::endl; + if (static_cast(s.length()) != width * height * 2 + PREFIX) { + errln("State representation must have length width * " "height * 2 + {}!", PREFIX); exit(1); } } - bool operator==(const State &other) const { return state == other.state; } +public: + auto operator==(const puzzle& other) const -> bool + { + return state == other.state; + } - bool operator!=(const State &other) const { return !(*this == other); } + auto operator!=(const puzzle& other) const -> bool + { + return !(*this == other); + } - BlockIterator begin() const { - BlockIterator it = BlockIterator(*this); - if (!(*it).IsValid()) { + [[nodiscard]] auto begin() const -> block_iterator + { + block_iterator it{*this}; + if (!(*it).valid()) { ++it; } return it; } - BlockIterator end() const { return BlockIterator(*this, width * height); } + [[nodiscard]] auto end() const -> block_iterator + { + return {*this, width * height}; + } + +private: + [[nodiscard]] auto get_index(int x, int y) const -> int; public: - auto Hash() const -> std::size_t; + [[nodiscard]] auto hash() const -> size_t; + [[nodiscard]] auto has_win_condition() const -> bool; + [[nodiscard]] auto won() const -> bool; + [[nodiscard]] auto valid() const -> bool; + [[nodiscard]] auto valid_thorough() const -> bool; - auto HasWinCondition() const -> bool; + // Repr helpers - auto IsWon() const -> bool; + [[nodiscard]] auto try_get_block(int x, int y) const -> std::optional; + [[nodiscard]] auto try_get_target_block() const -> std::optional; + [[nodiscard]] auto covers(int x, int y, int w, int h) const -> bool; + [[nodiscard]] auto covers(int x, int y) const -> bool; + [[nodiscard]] auto covers(const block& b) const -> bool; - auto SetGoal(int x, int y) -> bool; + // Editing - auto ClearGoal() -> void; + [[nodiscard]] auto try_toggle_restricted() const -> std::optional; + [[nodiscard]] auto try_set_goal(int x, int y) const -> std::optional; + [[nodiscard]] auto try_clear_goal() const -> std::optional; + [[nodiscard]] auto try_add_column() const -> std::optional; + [[nodiscard]] auto try_remove_column() const -> std::optional; + [[nodiscard]] auto try_add_row() const -> std::optional; + [[nodiscard]] auto try_remove_row() const -> std::optional; + [[nodiscard]] auto try_add_block(const block& b) const -> std::optional; + [[nodiscard]] auto try_remove_block(int x, int y) const -> std::optional; + [[nodiscard]] auto try_toggle_target(int x, int y) const -> std::optional; + [[nodiscard]] auto try_toggle_wall(int x, int y) const -> std::optional; - auto AddColumn() const -> State; + // Playing - auto RemoveColumn() const -> State; + [[nodiscard]] auto try_move_block_at(int x, int y, direction dir) const -> std::optional; - auto AddRow() const -> State; + // Statespace - auto RemoveRow() const -> State; - - auto AddBlock(const Block &block) -> bool; - - auto GetBlock(int x, int y) const -> Block; - - auto GetBlockAt(int x, int y) const -> std::string; - - auto GetTargetBlock() const -> Block; - - auto GetIndex(int x, int y) const -> int; - - auto RemoveBlock(int x, int y) -> bool; - - auto ToggleTarget(int x, int y) -> bool; - - auto ToggleWall(int x, int y) -> bool; - - auto ToggleRestricted() -> void; - - auto MoveBlockAt(int x, int y, Direction dir) -> bool; - - auto GetNextStates() const -> std::vector; - - auto Closure() const - -> std::pair, - std::vector>>; + [[nodiscard]] auto find_adjacent_puzzles() const -> std::vector; + [[nodiscard]] auto + explore_state_space() const -> std::pair, std::vector>>; }; // Provide hash functions so we can use State and as hash-set // keys for masses and springs. -template <> struct std::hash { - std::size_t operator()(const State &s) const noexcept { return s.Hash(); } -}; - -template <> struct std::hash> { - std::size_t operator()(const std::pair &s) const noexcept { - auto h1 = std::hash{}(s.first); - auto h2 = std::hash{}(s.second); - return h1 + h2 + (h1 * h2); +template <> +struct std::hash +{ + auto operator()(const puzzle& s) const noexcept -> size_t + { + return s.hash(); } }; -template <> struct std::equal_to> { - bool operator()(const std::pair &a, - const std::pair &b) const noexcept { - return (a.first == b.first && a.second == b.second) || - (a.first == b.second && a.second == b.first); +template <> +struct std::hash> +{ + auto operator()(const std::pair& s) const noexcept -> size_t + { + const size_t h1 = std::hash{}(s.first); + const size_t h2 = std::hash{}(s.second); + + return h1 + h2 + h1 * h2; + // return (h1 ^ h2) + 0x9e3779b9 + (std::min(h1, h2) << 6) + (std::max(h1, h2) >> 2); } }; -using WinCondition = std::function; +template <> +struct std::equal_to> +{ + auto operator()(const std::pair& a, const std::pair& b) const noexcept -> bool + { + return (a.first == b.first && a.second == b.second) || (a.first == b.second && a.second == b.first); + } +}; -#endif +using win_condition = std::function; + +#endif \ No newline at end of file diff --git a/include/renderer.hpp b/include/renderer.hpp index 8d48139..7938767 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -1,80 +1,99 @@ -#ifndef __RENDERER_HPP_ -#define __RENDERER_HPP_ +#ifndef RENDERER_HPP_ +#define RENDERER_HPP_ #include "camera.hpp" #include "config.hpp" -#include "gui.hpp" +#include "user_interface.hpp" #include "input.hpp" -#include "state.hpp" +#include "state_manager.hpp" #include -#include +#include -class Renderer { +class renderer +{ private: - const StateManager &state; - const InputHandler &input; - Gui &gui; + const state_manager& state; + const input_handler& input; + user_interface& gui; - const OrbitCamera3D &camera; - RenderTexture render_target; - RenderTexture klotski_target; - RenderTexture menu_target; + const orbit_camera& camera; + RenderTexture render_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT); + RenderTexture klotski_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT); + RenderTexture menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT); - // Instancing - Material vertex_mat; - std::size_t transforms_size = 0; - Matrix *transforms = nullptr; - Mesh cube_instance; - Shader instancing_shader; - -public: - Renderer(const OrbitCamera3D &_camera, const StateManager &_state, - const InputHandler &_input, Gui &_gui) - : state(_state), input(_input), gui(_gui), camera(_camera) { - 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; - Renderer &operator=(const Renderer ©) = delete; - Renderer(Renderer &&move) = delete; - Renderer &operator=(Renderer &&move) = delete; - - ~Renderer() { - UnloadRenderTexture(render_target); - UnloadRenderTexture(klotski_target); - UnloadRenderTexture(menu_target); + // Batching + std::vector> connections; // Instancing - if (transforms != nullptr) { - UnloadMaterial(vertex_mat); - MemFree(transforms); - UnloadMesh(cube_instance); + static constexpr int INSTANCE_COLOR_ATTR = 5; + std::vector transforms; + std::vector colors; + Material vertex_mat = LoadMaterialDefault(); + Mesh cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE); + Shader instancing_shader = LoadShader("shader/instancing_vertex.glsl", "shader/instancing_fragment.glsl"); - // I think the shader already gets unloaded with the material? - // UnloadShader(instancing_shader); - } - } - -private: - auto AllocateGraphInstancing(std::size_t size) -> void; - - auto ReallocateGraphInstancingIfNecessary(std::size_t size) -> void; + unsigned int color_vbo_id = 0; public: - auto UpdateTextureSizes() -> void; + renderer(const orbit_camera& _camera, const state_manager& _state, const input_handler& _input, + user_interface& _gui) + : state(_state), input(_input), gui(_gui), camera(_camera) + { + instancing_shader.locs[SHADER_LOC_MATRIX_MVP] = GetShaderLocation(instancing_shader, "mvp"); + instancing_shader.locs[SHADER_LOC_MATRIX_MODEL] = GetShaderLocationAttrib(instancing_shader, "instanceTransform"); + instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(instancing_shader, "viewPos"); - auto DrawMassSprings(const std::vector &masses) -> void; + infoln("LOC vertexPosition: {}", rlGetLocationAttrib(instancing_shader.id, "vertexPosition")); + infoln("LOC instanceTransform: {}", rlGetLocationAttrib(instancing_shader.id, "instanceTransform")); + infoln("LOC instanceColor: {}", rlGetLocationAttrib(instancing_shader.id, "instanceColor")); - auto DrawKlotski() -> void; + // vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR; + vertex_mat.shader = instancing_shader; - auto DrawMenu(const std::vector &masses) -> void; + transforms.reserve(DRAW_VERTICES_LIMIT); + colors.reserve(DRAW_VERTICES_LIMIT); + color_vbo_id = rlLoadVertexBuffer(colors.data(), DRAW_VERTICES_LIMIT * sizeof(Color), true); - auto DrawTextures(int fps, int ups) -> void; + rlEnableVertexArray(cube_instance.vaoId); + rlEnableVertexBuffer(color_vbo_id); + rlSetVertexAttribute(INSTANCE_COLOR_ATTR, 4, RL_UNSIGNED_BYTE, true, 0, 0); + rlEnableVertexAttribute(INSTANCE_COLOR_ATTR); + rlSetVertexAttributeDivisor(INSTANCE_COLOR_ATTR, 1); + + rlDisableVertexBuffer(); + rlDisableVertexArray(); + } + + renderer(const renderer& copy) = delete; + auto operator=(const renderer& copy) -> renderer& = delete; + renderer(renderer&& move) = delete; + auto operator=(renderer&& move) -> renderer& = delete; + + ~renderer() + { + UnloadRenderTexture(render_target); + UnloadRenderTexture(klotski_target); + UnloadRenderTexture(menu_target); + + // Instancing + UnloadMaterial(vertex_mat); + UnloadMesh(cube_instance); + + // I think the shader already gets unloaded with the material? + // UnloadShader(instancing_shader); + } + +private: + auto update_texture_sizes() -> void; + + auto draw_mass_springs(const std::vector& masses) -> void; + auto draw_klotski() const -> void; + auto draw_menu() const -> void; + auto draw_textures(int fps, int ups, size_t mass_count, size_t spring_count) const -> void; + +public: + auto render(const std::vector& masses, int fps, int ups, size_t mass_count, size_t spring_count) -> void; }; -#endif +#endif \ No newline at end of file diff --git a/include/state.hpp b/include/state.hpp deleted file mode 100644 index 14f6533..0000000 --- a/include/state.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef __STATE_HPP_ -#define __STATE_HPP_ - -#include "config.hpp" -#include "distance.hpp" -#include "physics.hpp" -#include "puzzle.hpp" - -#include -#include -#include -#include - -class StateManager { -public: - ThreadedPhysics &physics; - - std::vector presets = {State()}; - std::vector comments = {"Empty"}; - - // Some stuff is faster to map from state to mass (e.g. in the renderer) - std::unordered_map states; // State to mass id - std::unordered_set winning_states; - std::unordered_map visited_states; // How often we've been here - std::stack history; - - // Other stuff maps from mass to state :/ - std::unordered_map masses; // Mass id to state - std::vector winning_path; - - // Fuck it, duplicate the springs too, we don't even need to copy them from - // the physics thread then... - std::vector> springs; - - // Distance calculation result can be buffered and reused to calculate a new - // path on the same graph - DistanceResult target_distances; - - std::string preset_file; - - int total_moves = 0; - int current_preset = 0; - State starting_state; - State current_state; - State previous_state; - - bool edited = false; - -public: - StateManager(ThreadedPhysics &_physics, const std::string &_preset_file) - : physics(_physics) { - ParsePresetFile(_preset_file); - current_state = presets.at(current_preset); - ClearGraph(); - } - - StateManager(const StateManager ©) = delete; - StateManager &operator=(const StateManager ©) = delete; - StateManager(StateManager &&move) = delete; - StateManager &operator=(StateManager &&move) = delete; - - ~StateManager() {} - -private: - auto ParsePresetFile(const std::string &_preset_file) -> bool; - -public: - auto AppendPresetFile(const std::string preset_name) -> void; - - auto LoadPreset(int preset) -> void; - - auto ResetState() -> void; - - auto PreviousPreset() -> void; - - auto NextPreset() -> void; - - auto NextPath() -> void; - - auto FillGraph() -> void; - - auto UpdateGraph() -> void; - - auto ClearGraph() -> void; - - auto FindWinningStates() -> void; - - auto FindTargetDistances() -> void; - - auto FindTargetPath() -> void; - - auto FindWorstState() -> State; - - auto GoToWorst() -> void; - - auto GoToNearestTarget() -> void; - - auto PopHistory() -> void; - - auto CurrentMassIndex() const -> std::size_t; -}; - -#endif diff --git a/include/state_manager.hpp b/include/state_manager.hpp new file mode 100644 index 0000000..d639fc7 --- /dev/null +++ b/include/state_manager.hpp @@ -0,0 +1,152 @@ +#ifndef STATE_MANAGER_HPP_ +#define STATE_MANAGER_HPP_ + +#include "distance.hpp" +#include "physics.hpp" +#include "puzzle.hpp" + +#include +#include +#include + +class state_manager +{ +private: + threaded_physics& physics; + + std::string preset_file; + size_t current_preset = 0; + std::vector preset_states = {puzzle(4, 5, 9, 9, false)}; + std::vector preset_comments = {"Empty"}; + + // State storage (store states twice for bidirectional lookup). + // Everything else should only store indices to state_pool. + + std::vector state_pool; // Indices are equal to mass_springs mass indices + std::unordered_map state_indices; // Maps states to indices + std::vector> links; // Indices are equal to mass_springs springs indices + + graph_distances node_target_distances; // Buffered and reused if the graph doesn't change + std::unordered_set winning_indices; // Indices of all states where the board is solved + std::vector winning_path; // Ordered list of node indices leading to the nearest solved state + std::unordered_set path_indices; // For faster lookup if a vertex is part of the path in renderer + + std::stack move_history; // Moves between the starting state and the current state + std::unordered_map visit_counts; // How often each state was visited + + size_t starting_state_index = 0; + size_t current_state_index = 0; + size_t previous_state_index = 0; + + int total_moves = 0; + +public: + state_manager(threaded_physics& _physics, const std::string& _preset_file) + : physics(_physics) + { + parse_preset_file(_preset_file); + load_preset(0); + } + + state_manager(const state_manager& copy) = delete; + auto operator=(const state_manager& copy) -> state_manager& = delete; + state_manager(state_manager&& move) = delete; + auto operator=(state_manager&& move) -> state_manager& = delete; + +private: + /** + * Inserts a board state into the state_manager and the physics system. + * States should only be inserted using this function to keep both systems in sync. + * The function checks for duplicates before insertion. + * + * @param state State to insert + * @return Index of insertion (or existing index if duplicate) + */ + auto synced_try_insert_state(const puzzle& state) -> size_t; + + /** + * Inserts a state link into the state_manager and the physics system. + * Links should only be inserted using this function to keep both systems in sync. + * The function does not check for duplicates before insertion. + * + * @param first_index Index of the first linked state + * @param second_index Index of the second linked state + */ + auto synced_insert_link(size_t first_index, size_t second_index) -> void; + + /** + * Inserts an entire statespace into the state_manager and the physics system. + * If inserting many states and links in bulk, this function should always be used + * to not stress the physics command mutex. + * The function does not check for duplicates before insertion. + * + * @param states List of states to insert + * @param _links List of links to insert + */ + auto synced_insert_statespace(const std::vector& states, + const std::vector>& _links) -> void; + + /** + * Clears all states and links (and related) from the state_manager and the physics system. + * Note that this leaves any dangling indices (e.g., current_state_index) in an invalid state. + */ + auto synced_clear_statespace() -> void; + +public: + // Presets + + auto parse_preset_file(const std::string& _preset_file) -> bool; + auto append_preset_file(const std::string& preset_name) -> bool; + auto load_preset(size_t preset) -> void; + auto load_previous_preset() -> void; + auto load_next_preset() -> void; + + // Update current_state + + auto update_current_state(const puzzle& p) -> void; + auto edit_starting_state(const puzzle& p) -> void; + auto goto_starting_state() -> void; + auto goto_optimal_next_state() -> void; + auto goto_previous_state() -> void; + auto goto_most_distant_state() -> void; + auto goto_closest_target_state() -> void; + + // Update graph + + auto populate_graph() -> void; + auto clear_graph_and_add_current(const puzzle& p) -> void; + auto clear_graph_and_add_current() -> void; + auto populate_winning_indices() -> void; + auto populate_node_target_distances() -> void; + auto populate_winning_path() -> void; + + // Index mapping + + [[nodiscard]] auto get_index(const puzzle& state) const -> size_t; + [[nodiscard]] auto get_current_index() const -> size_t; + [[nodiscard]] auto get_starting_index() const -> size_t; + [[nodiscard]] auto get_state(size_t index) const -> const puzzle&; + [[nodiscard]] auto get_current_state() const -> const puzzle&; + [[nodiscard]] auto get_starting_state() const -> const puzzle&; + + // Access + [[nodiscard]] auto get_state_count() const -> size_t; + [[nodiscard]] auto get_target_count() const -> size_t; + [[nodiscard]] auto get_link_count() const -> size_t; + [[nodiscard]] auto get_path_length() const -> size_t; + [[nodiscard]] auto get_links() const -> const std::vector>&; + [[nodiscard]] auto get_winning_indices() const -> const std::unordered_set&; + [[nodiscard]] auto get_visit_counts() const -> const std::unordered_map&; + [[nodiscard]] auto get_winning_path() const -> const std::vector&; + [[nodiscard]] auto get_path_indices() const -> const std::unordered_set&; + [[nodiscard]] auto get_current_visits() const -> int; + [[nodiscard]] auto get_current_preset() const -> size_t; + [[nodiscard]] auto get_preset_count() const -> size_t; + [[nodiscard]] auto get_current_preset_comment() const -> const std::string&; + [[nodiscard]] auto has_history() const -> bool; + [[nodiscard]] auto has_distances() const -> bool; + [[nodiscard]] auto get_total_moves() const -> size_t; + [[nodiscard]] auto was_edited() const -> bool; +}; + +#endif \ No newline at end of file diff --git a/include/tracy.hpp b/include/tracy.hpp deleted file mode 100644 index 5aa0e82..0000000 --- a/include/tracy.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef __TRACY_HPP_ -#define __TRACY_HPP_ - -#include "config.hpp" - -#ifdef TRACY - -#include - -void *operator new(std::size_t count); -void operator delete(void *ptr) noexcept; -void operator delete(void *ptr, std::size_t count) noexcept; - -#endif - -#endif diff --git a/include/user_interface.hpp b/include/user_interface.hpp new file mode 100644 index 0000000..9e8aaa4 --- /dev/null +++ b/include/user_interface.hpp @@ -0,0 +1,166 @@ +#ifndef GUI_HPP_ +#define GUI_HPP_ + +#include "camera.hpp" +#include "config.hpp" +#include "input.hpp" +#include "state_manager.hpp" + +#include + +class user_interface +{ + class grid + { + public: + int x; + int y; + int width; + int height; + int columns; + int rows; + const int padding; + + public: + grid(const int _x, const int _y, const int _width, const int _height, const int _columns, const int _rows, + const int _padding) + : x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows), padding(_padding) {} + + public: + auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows) -> void; + auto update_bounds(int _x, int _y, int _width, int _height) -> void; + auto update_bounds(int _x, int _y) -> void; + + [[nodiscard]] auto bounds() const -> Rectangle; + [[nodiscard]] auto bounds(int _x, int _y, int _width, int _height) const -> Rectangle; + + [[nodiscard]] auto square_bounds() const -> Rectangle; + [[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const -> Rectangle; + }; + + struct style + { + int border_color_normal; + int base_color_normal; + int text_color_normal; + + int border_color_focused; + int base_color_focused; + int text_color_focused; + + int border_color_pressed; + int base_color_pressed; + int text_color_pressed; + + int border_color_disabled; + int base_color_disabled; + int text_color_disabled; + }; + + struct default_style : style + { + int background_color; + int line_color; + + int text_size; + int text_spacing; + int text_line_spacing; + int text_alignment_vertical; + int text_wrap_mode; + }; + + struct component_style : style + { + int border_width; + int text_padding; + int text_alignment; + }; + +private: + input_handler& input; + state_manager& state; + const orbit_camera& camera; + + grid menu_grid = grid(0, 0, GetScreenWidth(), MENU_HEIGHT, MENU_COLS, MENU_ROWS, MENU_PAD); + + grid board_grid = grid(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT, + state.get_current_state().width, state.get_current_state().height, BOARD_PADDING); + + grid graph_overlay_grid = grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD); + + grid debug_overlay_grid = grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD); + + bool save_window = false; + std::array preset_name = {}; + bool help_window = false; + +public: + user_interface(input_handler& _input, state_manager& _state, const orbit_camera& _camera) + : input(_input), state(_state), camera(_camera) + { + init(); + } + + user_interface(const user_interface& copy) = delete; + auto operator=(const user_interface& copy) -> user_interface& = delete; + user_interface(user_interface&& move) = delete; + auto operator=(user_interface&& move) -> user_interface& = delete; + +private: + static auto init() -> void; + + static auto apply_color(style& style, Color color) -> void; + static auto apply_block_color(style& style, Color color) -> void; + static auto apply_text_color(style& style, Color color) -> void; + + static auto get_default_style() -> default_style; + static auto set_default_style(const default_style& style) -> void; + static auto get_component_style(int component) -> component_style; + static auto set_component_style(int component, const component_style& style) -> void; + + auto draw_button(Rectangle bounds, const std::string& label, Color color, bool enabled = true, + int font_size = FONT_SIZE) const -> int; + + auto draw_menu_button(int x, int y, int width, int height, const std::string& label, Color color, bool enabled = true, + int font_size = FONT_SIZE) const -> int; + + auto draw_toggle_slider(Rectangle bounds, const std::string& off_label, const std::string& on_label, int* active, + Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int; + + auto draw_menu_toggle_slider(int x, int y, int width, int height, const std::string& off_label, + const std::string& on_label, int* active, Color color, bool enabled = true, + int font_size = FONT_SIZE) const -> int; + + auto draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max, Color color, + bool enabled = true, int font_size = FONT_SIZE) const -> int; + + auto draw_menu_spinner(int x, int y, int width, int height, const std::string& label, int* value, int min, int max, + Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int; + + auto draw_label(Rectangle bounds, const std::string& text, Color color, bool enabled = true, + int font_size = FONT_SIZE) const -> int; + + auto draw_board_block(int x, int y, int width, int height, Color color, bool enabled = true) const -> bool; + + [[nodiscard]] auto window_open() const -> bool; + + // Different menu sections + auto draw_menu_header(Color color) const -> void; + auto draw_graph_info(Color color) const -> void; + auto draw_graph_controls(Color color) const -> void; + auto draw_camera_controls(Color color) const -> void; + auto draw_puzzle_controls(Color color) const -> void; + auto draw_edit_controls(Color color) const -> void; + auto draw_menu_footer(Color color) -> void; + +public: + static auto get_background_color() -> Color; + auto help_popup() -> void; + auto draw_save_preset_popup() -> void; + auto draw_main_menu() -> void; + auto draw_puzzle_board() -> void; + auto draw_graph_overlay(int fps, int ups, size_t mass_count, size_t spring_count) -> void; + auto update() const -> void; +}; + +#endif \ No newline at end of file diff --git a/include/util.hpp b/include/util.hpp index dcb0f3f..ec994a2 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -1,5 +1,5 @@ -#ifndef __UTIL_HPP_ -#define __UTIL_HPP_ +#ifndef UTIL_HPP_ +#define UTIL_HPP_ #include "config.hpp" @@ -7,14 +7,83 @@ #include #include -inline std::ostream &operator<<(std::ostream &os, const Vector2 &v) { - os << "(" << v.x << ", " << v.y << ")"; - return os; +inline auto operator<<(std::ostream& os, const Vector2& v) -> std::ostream& +{ + os << "(" << v.x << ", " << v.y << ")"; + return os; } -inline std::ostream &operator<<(std::ostream &os, const Vector3 &v) { - os << "(" << v.x << ", " << v.y << ", " << v.z << ")"; - return os; +inline auto operator<<(std::ostream& os, const Vector3& v) -> std::ostream& +{ + os << "(" << v.x << ", " << v.y << ", " << v.z << ")"; + return os; } -#endif +enum ctrl +{ + reset = 0, + bold_bright = 1, + underline = 4, + inverse = 7, + bold_bright_off = 21, + underline_off = 24, + inverse_off = 27 +}; + +enum fg +{ + fg_black = 30, + fg_red = 31, + fg_green = 32, + fg_yellow = 33, + fg_blue = 34, + fg_magenta = 35, + fg_cyan = 36, + fg_white = 37 +}; + +enum bg +{ + bg_black = 40, + bg_red = 41, + bg_green = 42, + bg_yellow = 43, + bg_blue = 44, + bg_magenta = 45, + bg_cyan = 46, + bg_white = 47 +}; + +inline auto ansi_bold_fg(const fg color) -> std::string +{ + return std::format("\033[1;{}m", static_cast(color)); +} + +inline auto ansi_reset() -> std::string +{ + return "\033[0m"; +} + +// std::println doesn't work with mingw +template +auto infoln(std::format_string fmt, Args&&... args) -> void +{ + std::cout << std::format("[{}INFO{}]: ", ansi_bold_fg(fg_blue), ansi_reset()) << std::format( + fmt, std::forward(args)...) << std::endl; +} + +template +auto warnln(std::format_string fmt, Args&&... args) -> void +{ + std::cout << std::format("[{}WARNING{}]: ", ansi_bold_fg(fg_yellow), ansi_reset()) << std::format( + fmt, std::forward(args)...) << std::endl; +} + +template +auto errln(std::format_string fmt, Args&&... args) -> void +{ + std::cout << std::format("[{}ERROR{}]: ", ansi_bold_fg(fg_red), ansi_reset()) << std::format( + fmt, std::forward(args)...) << std::endl; +} + +#endif \ No newline at end of file diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..cff4e298c5aa40cedb2422f4af2cc2718bec04e2 GIT binary patch literal 333947 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

|`mkFZ*G7?ESw`6`JSBMRGd+@KYmr`=A$?<# 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 VKVnD$>=D7BG>=Cp4MqeUlT-1s^i}X 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

rM^^|ul2JIinufStsZSv^5V)y>L*6~ zCB+1%1Ts(|oIL~qAc6R|vznEs>Y%BGk|MtGIpDjIuWobjecI{a0R6rrQu@quQW^Tm zx^O67I=y5=@{GzpMJO@Go!lRLHTD;4AOm%RqttxC z9o{2@wMo_~Ju^lS!*pG{x>?#L$Ym(9el1RXYjik=mS+6?5^Z zPiNLFU&wA+%67s*XzFimKi7L}6y~10K-=uv3(q)Y!Y`k-IfS}O@e7(MI*<9LUklxOqKVbZl1PCLT0g;HTggRwyB9zFSQ;`lOQsY*)DE)iGL|;eJAA9& zZo1jcPhJfO9#nyf@z~!DaBhP; zHCU@-L69uA^9y|Z=ermA0EXzFdg}uTfaoc@7V72dw4#8$<-e{+=TNYfAav1 zzRUIwZH*Qvbuc;-+gX<0z?`(EO?zv_b~qPY&rXYOH;ZP9%KIXh2b)r9dYUOGYZK4e z`yhz8;VbyA*8QX`K*0x1C3HzV6y7i5@*JK%!#W&-Yj;mh7FPSwRYTN3t1pT$`i5!%eiG$-~Ds& zhY8V%oHqqemKwRghC8TKYlqT_Q|(W6cU{Je^`b){5FlFDvecgzy$4pF*S=5(T)M-= z!M{TukLYc_4eR7?G|(_Ds+4ke9+lj;d{1KN34fCQh_BEevnZ2f#9p01OR=#ecqt|2d-w6E zB}OLQjTLaaB;r2Q3IFBT*plIm&C$&dQ^X4Z1R3WDaPt|SZ&%O7eetdE`OsMjG&x|m zIO3*_N#160Gu{6SD0=~MS03YedcWt~+`CWI&T!o^lKrHr8)jcLZg(Z-7m01KE^4~} z+p`U1$EQM^eYH=mRqs*8G^)uQ0mZE@@h>20uNmO37QuX*U=clr?VimZut9Wj0_w(T zNbUHw1(H?v^lmB_b}Yo920C`BP7-+0^`^RPmBQ&?4JPo6OQ1D{bZS^842F@K-S&cH zKk!^QF^HX+NU7*{mmngE|GB z0pspOio3bF#l5{EAiN~T_7l06>@^VI7-^q6gQ>W;k#xSKUM-X-w}W|3c8fNwy*GtN zwEIYbEqsZGQrV-6^=3avTJgO%*z;lQ+(%SZMaAUq!jPFL{lMo^9ya$JL%P^n&r@Yu7GD-3d?0=m=8AL`Q0_n89fhPfb-T zNi!MI=Xht_8{(p>1a^1DVpYLY_&HZ;SM6N2_=I)g*vGY#q}5%j%2mocf2ngOJo z1j%TT>FfWa?K`94-rBcwa^fT+l|&07jNXFih7(a{)aae)o#+Nb3Ssm*I?;(1-Heuq zUSo8IiQY$)G0Z6MIBUJ{TF?JE@5lfCU@gnH%ijBU-`92B*H%)_^)^yI{Oy0HSeb># zRcon_?{%xZcb|1Z*OZ^0X*uX$L3x$#eAOiu{q_HC!+-Vh_g9%PNNb)tj`EZEY~b$? zzewNNr$PJYGo(*RCts}D8&}@f=2bxwk!0W4s)f`|)BdK8)9HN!#bxR&B=o3DYS`}= z0QO%9ZNq3HEECy~3O!*uVgfvV1%Aw^AP-BeZ;!}$j}9NQ$f;{_!Sz2Y`c!Y(w3{EG zUg@pS*4dQa_lm^yJP_n~Sjr>O3H0C{JD%hUMizNkHB1 z!Wc|kd+z4hg$Dn5N9+D%fMFW9#dX1Q1G9i~>g%4AYtvR8aR@KjTdMGf5dn%ExZc|~^%K;dv#nnR@rNE?ZDSGA zIHKv=f80H!UmBkE<&FCY9=zBf%bJ%P2{6kBF<+y-nRDXtH-4MB2_(5`YeoH2oz2w{ zCiO7@m51tFR^jt`DrD#}TAr%7aGOOrGSW>EQs|ZQNeg8ZU^Y|>w*J5b zVNKSAHw!6Kba|3nF)kqs53CVt645Ve(WXBnyAjCobWMNl(-UmE6^NhBa1cf?%JHUK zY@^=?Gq^>o3AkQSYp@l*ShvIyll`a~kHu?zZlQz?e6YG}HQ{gdzi;EI3Ev%aIJ3dl z+YMnOmeK}RTG>2fsml1*U-UWp?=Czny@qTgwkGk^>fIaYQ#%%{0jP(ESw`baD9 zamd|Q0!Ojb%#9RtP{|=ZyQVj?W_49G$x4yS0R=g4v7=IM3`H7rs~XDurnam-XZDMi zh>MR;P95D09XH}1Aw8qz8*g!aPO`U7HZ`5e4wdtNJ?%RGapqW!5#^PB(!x}&X!`16 z7yG@CC5g-G#7BFIuHX3l2z)Eo+<56_EwDG9+uPCkR58=7A?!)N8bz2{mc4XRNXt&O z#P_1)TxB`Eu=wLijt6~0o|bR81l1zmfm}SBgVCTx-jnfrWBaIEGa>#w`N-RJrDl^c}&8y$adj>QXI}yBq70# z`teqME#m4 zt6RmPU#8cfhIW<19+-b&ic%3f{VAb^EoN#&ehd_9cFWO5Kq8F&x@!inuG@Y+o|lfe5zRV%sDwgq_FaDx}rNVrEJf{(`q~5vvsTOsvrv*@+QC z34u%mdc=)a5HoX2eGTK(6~Ygx^?b<-Mn0?lfJi$dP z1XK_>x}Vejk9whRH*5X*j0LBgyp%}wk`S}+eSqI5DY;{jk5KMDSVUhd;=svwv7#0h z^2>%%U}qEQg>x8y{!(ddIz2@wI`5!LJ&w@%k2;atez&1teRhmUrwudR&zKlMjqqPx ziKFj0+?JB%(WV85h(NXAY57PO-fCy9@&oVdX2gIWTiUuK-y$Po_XK?nwX(83%hyeb zXHEq3N?yJoL+=Tj^{tH8AoVhfgo~U^?F}V=EH)^Z-cuKIxaDN7VLk^>?LyZxt*r6G zTsa_vB$m)&^T`jIWgzLAcDDIfV((qw7FEE_%jT&+oNpL=YaRi{!|7$>!4Zr4+4JMx0H*PRm+qwJBz%KyuYH1|CRE7TMipFxH!7bz$rawAz=@D$eQ;*oWby&jgb!W3FZS58M)Nkz8NtwL#J3w2U+~J z;9&&QXqT+HJYE=2EWfj41F-rIHLpDar@jm2C5O0pNXcgUxZmO%)CMoE{LtqO;TUy2w zOZUOJI=PI#T@wA8Hl>`MA1osxmtCavGcpJN6+7SHJ6!CN1U86DXi2Y0N|GwMeEq1| zZ>13J7Qx)Mc@Zc$-&`PWPTG{;ivH1k{u8!q!ub#X(1QnrvI`K8ggDn%JTG#xutF4G zY1{76js;PZUn(3HLVO>kV4%zaDOCz<=c?LOYH@ zO_b0PNjrdME48WBMgMkUO_)toNLbwOLShf9)-L41+sf3p%a=@~`qNcUhYiM9Qz)hV zw^1L39wF^TslJobXhVTU{m73T;_rQ$glbz;I}__xCdt6E=3q=BwR$b#O1%DYnJviXkW5&<~QuO2b8mCE(7JfnoqQ zr_2ni#dGufmqsipfb@Vv?`_U-BU9sg%It}~G7#!JDx@WIXU;jrdmKN}`+hy&-(|Mh ztrb#yDdU~K=)-yxd0JK}w*F-|(2ay34b^|OfF#yQ_)}!aSZIfu@+h^&yHp+ZRNK*J zHr{B{*lc+kuwKXWf>3bRT|Rpf0 zSR7z$XxTQg=}VOTM>tieLBkt1Afm2S`QX>roqEN(O*I@BdU2x$*Knyl@^m1Wwm&P}US>LK)?Qi96G z5B1QEs)G@2!~kJ}OW6Jy@1lrY0b0R+rphwV#TVw_ob+OMy?qx@y}F>A5cjv}uG>`& zkb3uD0Q*|Gv69lJ(Am|sC0v=pcEg3!)PFK{Nly`Q+koh=oj!|P8D_NmqlwF%ksfTI z388Z@D~(i68Fi2%_#d1#=NNixEYv?Be->|@PnlN-uw2mQu`=wj4Sl*FI8_m>3us9D z5dTbAq{J)RG2}sfqQpl49 z-UhO3y67ytZrggygabw(qj{>+aL?s)GFyrVKm>R77XbvH+8i1M;S>$BF>i_~xQ7kD z#4tC8T7;)3OSAZ|PNbshmeUDU3)1NlffYc(a4#gXM62V0sF2sSSzV`wJxf| zOCRQ`Vt>0ZukutiPXsS(K7Hed(?~t z?y}n$!BqO?3IP@I_I1u!frZ+*di5-`hYqTdMdi#R6-WW|%Yeg8vl^KvGgUnh(fE}m zGKp6#gRtkY&#a+*%XC{c^ztY*cl6_sqB!x@oKR_7(#f4~#-M>nv3LTUYKNZwyM>!a zd+V+PI+86|#CsnM)+*=?x3O)e-Tk~x+ugwRrKu)d6nmDE82^bEK5%$lD|%&B6huwQ z6?}k1wr~50Y_k(SW?w3Lj#si*tHJ4tkT8`5dnzlJ{WBK zWs3MFf#{1#?Ttu$d9eMkl~g*uS$;`U7XQb3mv-2zLW4%ZXa>pp_bqYT=~l5jIk7QZ z?4g72GUe+^RpY*~j!;CNczzfHvD5bdk!A2et5WAc?e7L%@%8rtm?fsO!7!7jl39vy zu1iInW}giFXI|qefgxb4>w9s%dXS%07u%H7KU!=%epx4*PU)TFSUzbMJ*ham%O77w z56fSZsrRE~+}`T}%|wtTZzDaVjfB2tdy4auSTX8xgX)4$iwz33djXdOzyg$~v(Pyhs%dfw?GG~XH`{8k={5>sql^d=P?&fKv?1a9t>_}UDyyB}OkT~s+zXQEwQJF@ zkui(fanpt(kIF>dxd0a4Q!bl{`W=E#Py6ZczwCUP>AxhvqXKGWeCPh!W$lU}1$^wh zRRUG^y*+T#LrR9$M0RL>GMP<6RH>PxI(j-coeKRYk{pU^I@b+7!b7>LV3JAJi8AqD zRHbNW$&DpO3-!?46IE;UT6wTNo7CSnIA7?wd{AN!Ii!p;T4G{k?uLA`D?ga5Kbkxk z=oG{~A)(}Yhiqmnh+6MyEYZY)!NAh6>Eitdd+)QzW)hcwi26+P?Vc%-gBb7Y4B z13k(Qt$!oHa7S`3V_1R6$u>>K)0y_vTM9Cux>-xk~&nv#E^%Lj< z1^rx>62GLaEZqMvM6~s@8HFPBKmkM-F`PoKq?K^EDY6teaz)W+e0l~1mlkT=86_s} zeR^$OUpf)23IWRyKkVpJB9w*ne9c*&RlcxOAnl*L0vDYeK4k`e%zYXxkg<>Lph+{! zvb1b&Poan7(;2x$5%qU=J!*v4UV+bI8Mjg`lIpCLk)s+wng3{M&y+&O!#L|}T= zuLUWs|F3rhGDO4=HCM=~8;tfO&5BU&KKg$S4x6wB^@K$e#lLP(*~g{`X^R*0OyMXB z4w@h3Dtp;9mg;G32@}iZ$0g)?Jx4L$r{kciST`9S3x00>BBD;OF`FhQ2aCvhq2zF8eVOB#IR^jf@ zks-Op_6y!sBaDxpmJYx>ROWRYUimN*QiU`pb6ndY#e)ec7@86r!M?2@@~c!TWk$VI zmI?YTwY72^P2@3DXTzl3{SIg0`rGY)qvyOvdRXl1EeVj)4B4nhZexZDHK%}1xSTG| zAyMSR$yeP3xpH}U+cHOy&r4sMJY|>W4bfPvM7Ttne;UQ9-FqY2;J28bjDoM3E%D7$ zalal>U*N0F&P!tB{?=BFs?n-aJ}=CZU6to9i?W>O?&rrT@#Xg}*N#j+kYL$YE5I3$ zTO%Z6Cvte^ngEMrycXR0K^5UTJZd<`M3q8%-ffI zMF;yr!uX`VC0lD!Kb%WeQ+CsM!vf#jySCWQ`f&7qqHIc+yg1O3DZkf@Bp%KFd)BeeU8J8P2RyB(r3ev+o^0*Gs_|^ zwtZ%jHjh{Q+c;`VpD(jXQljgTSoC>Ki|bjLd@)LRIN7ik=LzQyJc` z)2Dm^>9_I_(tlc;uAgVu5PtwyE%(1{we}sSPJ2=APVo4s{0Gk{$3m$4<5qJUA{*Gd zTdLx>`X;n(v1Kj&pUN?V{9jJC4l7MT7}=$RkP`Ce$**8YXutu5{%qu2#X-g)&Ak5u z0lBJnbl-UDW$Zx9Ori@PIv1o$Vz%LPLNX zUj4 zFNmx;9hx~Nba99DuSn2#M2pxWU3RNQY7CB3>B5`)0*hD5-VTO$$@|w){iLq#*|4Jo zVTD-*CyowfJcO*&+WV77=iK-)f&^!@zPxR)D=+(0747}=4!Bz2;Ohd%PHRKE%N^1c z+kO1HreRIEtaAI*_3F&}Ux~K=V|`9Rb?mvixAzDLQ9DRySLGmoG09wfkU9nGtEnyZ zHK`&u{C|*pd>2o6nekf4xp5Vs=CH0Sg?>_O&gk-_F7)!E+aX6>gqOu^qm`jJ}(GaWNlM=&Wkf~B^uCm9vVlGX_t=3@J{(pg#(lzUH`|0}-Fk8~&F z&70rBn*-CxU5Hmk=zz{5;O-_8wcxTgB*)IVB=E&@2t*yqpkA>c=hDd)Dn=9*gE9X>(Yk5KYgsNf zHb$2O^mC?vX@1^Vg8$OmGLYPtYn+c{P#I}+<-gMwj5+&-v0wDb`DMj7p3~P#o6{o^ z{DT@O29p?=fH6h;jk71ECUpOzYulGS*`R)lb@H;DOMEr=AA-Ol!_Uz1fD3tlEaN5bPQ@-jPy<8Gq z$}VM(9al0%`_OKa?on-H`g)wKQyd;0>9}%_9?=jxfi4q$yv)w-{g3W}JI3dpuE$0$ z^LhTpu*hKrE~tn3@#(sJ59Ola(co&L3?BRwpE0gP-ix>piaffChfd9eT#-coxl&%3 zbsII2MqirZ5|Yi&7S0}D(!=tS>pR-nam8}!qZE^BFBKVOBNgzuj*?3iD7duL z=0<)jK?TJ6+#OIw;;SLoAm;Oj19dK(uVnVta@2*La(r3uD0tHqZjIlLtZ6YZEhm1J z!3)L|#7kq|Y0%Vkzxzg^GY|RnJ2^ZG==~Ev?X2(lZLwW)4C=bJXMXITzoZ?%4B;<~sF%p#1iT9>{pZFF|*Rd5~Yt=OoIJuhC5TKQ*0 zR-5_1`LaN2R_#L(N(r*aFPmAz?2P|i6dg8@Jucv(#N?=lCQQZ#` zhB@A4(dD81%DqkHQhS3bi}wY&B1`}Di2ki>%l#7Yx5;WF@i3cV5Q}GiXD)b!5`rNmt#QniXwl$KjmX)Cp zP+9mxNzi+-d}yInQr%0}#b z+)f;t-;a@ju^#E;cz>LWcy<8=l<>TUz_TQQE3n;LRhxs==xgb;XVJD(U zUOQfoliWqM1M z;#SrQ49)Tzn6%1oF9Up?eN@!5iFbsf?<&9<-97aS|Ciro#9D9et} zlTgvwQlB;x{U`)`l((ta_JU~Z1qsWP=l_c79h&~Cs&X5!0YWGCYn2`oSSyR0v&d=u zC+~q-@b?Nu+R+41BOfzO!N7RV*WJw*Y3tErRrlJTY`$if1oPB@!7^b5^>Kv5K^|LLNTdvsCqcSHlxGzT2R5{b{MyJ8yLW-_ zW;c8-i}@|VAV73V;-&sMq7?2bWKpi8ktRt!EXc;oQ(wv#;I&hKEE09l0o+!)jO_6+!ZjSYDuVAEJ zOYm9f=qRqophadN^ukKh;953_Zg-!M&3jq|!hN`?KdV%Pmue&)BxRq!eMtDKsv`5m z>=1ci31y83=!d!Ic&dq8lDHOK%aDwo)u5L!JQ@#7A-(^4qe)YlDT6N`TI5G zGx@pL!|hHF&hZ++gGkqQvLwnZ zepQjD6c4M928+0)I6QHJxYkmS1C|uZzH(N;wyRkue*i16?IGqvhPa47uzx zDQiLQ%f)4ZI~S$by@kyGhV`=Td%zEm#E)Ry_KyX{M>c>x#l? zdRT|5+5V=XQO%Zheh|cILizj)Xnu=QR_C^=QcF_2U>jvsN8*sQU)(LR+%d^N*<3#+O}7(Jl+FUO7<4s z{kLcEhWf=&#K2_F8-wo6BG)YIiHKY7#LVpq&+0u%JLSJcP!ZISfdr5j5n<~0M0@-q>ANAGIjij@=-Axog;=IjXvD6P3BR66`Z|nzA>s)%%VWc z&A5GD|g5AAPt%-9>9-T$^PAW z^p=)j`KOW{-(kB|jco>}%Ywtcrp|UfDg#$c&i7J zuyj|qdn^iH@Mia+vc@rJbIM@$-H`*P zVy-FqWC?Z1Hg1@%7@g2-gR4S|?@6&EDxcw#x9nRJmG{bE^88Z*=gC`>9MGZbPSP=+ zBQJYNevWrTq!~5RwfLW_+cr~SYig&hl=G*7P5E)SE<7{YINSx!I`%=H&Ro{}y9;|? z9wfPmDyEt`LdjY_^li`s1?q(HLIv%n0X|hRno8rm5VT%g8?OTo7p9Qn*b};9emRq7 zfl|phvNbDHv+8H7GQfD^o{xRYtSzjM>V+rCimMl+M0#NBh5LAESuruVj&>q1+$oHR z-=%}SO{;zasKd!e7{Tk(k|p+l^8gwL4?GV(x_d%`t(_M7vd9_Ym5`YvU~}1vOJ;Z3 z%!EWK=;@7;2g$LIS)mC}ViHGWr75&06PD7y%+-YmGIom8R@i}jb7HI7Gn=wE+D4g4 zQ`GqC&U6x5`}BW+BmLlhMMNv}Gz0;@;0ynI0!n=jOrt2;*hb(@v5gv7Q}$SL@H-N4 zU#qVM9{16xEee_okwBK2jmGj~); z?P!9B6<7FcLK@gnp-q!cc}tVa*eHGL!g28xMYyD(N_G{SAS9+XG{H3>M$aRuC-hOy z#zJU`l;lF3jnp4@)s5{AjtloaR8toIct49*KMrJbQ!`$$)g})2&gayd4&>$D@AdFp z=I1tsl}T`EwiO>8_RVK_w^bUu#@zR8W|O(so?>Gfxg@=!;FiMfAwtyc(2E zcbbd<2 zdQvt{9o7_ydchYKPnXmYJylg%nl)1cbOOYn&mzVBi*SmY;=q z+h4)UlE-6=9E1{dFA=U zAS5%TjC#v&nO9!ORC6q49j?`@Y#eW5^~pIBQLV9e2y6B3g=J|+6MTy5>Ir)#?x8%; zgSRc&8HxVv9aKU1F5JmhF$G+$<@a#He@ljrNU`V^hmveV5tKkOf*# zAZa%aGX1yI`%Z-+r`wZsI~~p_mvOou4L^B8UQqV{31Ca2MqOEUS?OMq^HXr|7tK{s zr~5`WcUi7)7+ImWdHaeyLjrUxqrBDOKJHD@RemuVO4=}y_f_Y(oAV;Ckm@bZ0})Mc z+g?#9!@ugp$&zX%ASo~-B>{P(AoJO#O8Drxz0R-)tN5lBs6nc<2VZ}Xocj$N&O#=(nn>YT>P3iA%&8OnnhpOu` zn^FRI)BuvJw|*&ls!RlYf8gw8IG0?o8}f*g=qT><3?O%xuq=+xdK@Br2*;uMAY2?j z;A?+E+HjQ>n@Y&wwHx&o5V?1c$bH>UBlDoz?_VH&U(MF6C7%F&_rc*L%#9iU4f9me zkmY1JgVJ#$1@x&ogOg?zO*>qD1+QcSEBK6FvPJCv&L8p3DJ-BAFgiwcndq^{ahZw8 z_4r$X|DuLxGE*f=Ms;l|wZA?5)pR)FF-!7yHFvEb)qApury!j;nC7qN_xSXxS6Ey=F7-{0oUWfyRw?T~+#We@y%BSNO-%@n{%x@{UzxLG49s0>nz_9Lai z-?mVed6m^7WL?d}6K)~IRR~b+lO!&mL0_<>QyP|&Z7w#aOttXZ_ZhQJ<~b#A36it9 zGw)2fq~5x*XlQbH6T9J}ACs_mXQnIRZJywyOk^IX6<|HJhpyCR5@@vH4YF9N_z}j19+}H3npxt_KODPp4;C?{TtyyJex$))Q=FC z1sM7fBx-+uyJ<+7nIs@JCH+g$Ki%{kzb3P+`wOtN*R$AxBD3 zya|Uy8%JqTE=O`cPfz}UP`_F1v3h5P+*YV-?2q8p*eoX*qQaUCm&?n-6NAX{u(?-` z6O~Q;z{nAzbsqZq5gKSfaq*=55#MrcRTWK}GT80ML4PTGq2$~upMG+Fqut)CFO6yTZ4Css>pm}J!G|f2EVldkvAV< zqFSFwjU?t}^MrlCY<`yxbZxOS1(_#$N3L0EPNDZIG6AInj5X62+2Zqs`wlIcYZ$?s z_~5gI9a5slHRFeum$wc7H^vsPncPkqvl6l#Ynme-Pwe399B7{1B?XQ$UX%&X<2Qoj z(wJCv*%4-o1b_EhnKs4ruh#is>I~zM(1~Scn_I0=gQ7SQA-o~!N!~m~6M^%nm3j|y zjV=IlETxI^2uq_|=6d67B9v z)BnORC(fIcI?$}Pe0&(Up+#N9y%xejWT*VXn#iNhKmGs)6qb2{iatQebIZQm{-t$dxECr z0F=@3w(*G%x)-o$PC+&fjYl^U>#zf`k~g=n@3qtPJ++07r+uze-uNg_EaV6Z0n=i52+woMx7)6WNBS09`72#VMe zqIiGE@=Ce=u8ZX7g-eo2DqX&0ZCY@lYO*=cE0ce0D>C0@eJjYg*U7IBna4DF{QADi z_@sxc8Z@EiuAtYX=hXzF>b?KgTfb3%PLkK6eCiByv_eUdpN3x9F3B)bwnoWIVTZ1brgpn&H$@=}q^gCA$uD2(T zSiQ4Njw*A4zj5%~ZOkEEfiU$-^~!{eP>h<~)Lzh_Nr?H@EBJF%JXkSu*7B!7f>0+^ z!CQo&s@}r28%Z;`x3xl5d-r3LCYPH$-fv_;6C;Idlyx7WDc~)eZzej-3DJkuv_k34 z8nB5J4hL<#`=xVLJtgW-s8;2de>{+NDr_F8L@t~kSJd|T82XIwrMtHnB=MFbqt-r{ z!fw&UL=&=<2gj^R^oC1v0S>7`8)HA}IzyS_+3`&M=2o!?)8Pu}p*>&WapK|Gi&YNF zlzp@S*%$e;!KMW0dT-EjmfNl5NnAiM)hPfOA(K0jVsnf;{In)EXr@mJP)(4SM=ZHD zxr4G_W(-fkDoD70E&Z34#aSpQcXz*PVKQ7o@A^lXhPvklMUrfmk@1RD7S4x#GS38A zlOMw)Yd$Vef2CF#Ah1ys&+U?uFDy9Kn}Q6LBGiiBEuy@H)A}D`N4SmM#)x_ToAQE& z-BbZj$jfpcrxK{0*F%6Kf9AA*S5uff&M_f`1}s+uZ;9gh&ahNUa;?qn6;0IhACqY( zWPAl4@@M%yn}PmA2nFr+tl>Po2XO=OCGPx$%I$f2&xB`c!}828aN#!%EdFaQ1i5Z zTY0p^oPXXbl6m)#iLp_X`|49}jXSxd5GjzD}q5u4Ry8wi!S=k=H`lv!#2$*sOepv+l)Lv{^mn+txEK4B4f+a4 z*-FionG;r5+KiVA!GRA~L)p|+GMBNf4BGjwm49D92Ag{aV;q5tpQb3-4v-0xoDFGNe z<3}I5BC^RLHeD^{XD*fUE{wKnQ+gj8rpqOr+08PuPfWO+PWKN^ECD8)81Zb2{#g$I z@Gpt&3+Um;;7G9SB*x})7>vx{4X8bAe5zq9!Sa2C@{AC4v)8)WcCZ1e0uSao79q9t zk^j~d-R(BhX$h#)U{l;Y%Hn23^Z17VRvr8O1l@O|!v0~~I`wi&0msam z(VhO=6v~;LW7YUi|_6$hF!ig5OvU-itWsE=wlytfm>BLzTCml2S}9q_y3Vd(p{T zR-P#?OkPXRO2xWh6E3n$)$QY;-q)rSlA3y34~cwYgh zci@qu?-7LTB0#BZQG zY6m94MSs|IrYGE@q#R6_Sr2{wY*a)pj56?p7faO`$7?ke{y%z+rYl!zYgR?Xp~l&xMLFQyZ4^{+~Y9Q)@$ zgtcq5v27B<9G1y3d_$&IBY%dqVJ@?=rD41(5t+j?HAW{c`2(be>0RWXq!RLq(R$-Z z`uYFKNV_&iC-9IDNIX>JK1k}k+vj7<>B|2Tf6IM{Qdzn_@(Zro2&7z@Q(%+jI_7sE z_b%A%S#){I?Pn#NOu7YztMa!F{a&3^-^d0zkHJ9Qz-Z6QNs%F!T?E`(`L7As={BvJqAYzdEj*uoNa-78~e}I1Cq8#`oK!l z5P3KotYKUA8)LUYuy}I;a5)Qgp(tr>NldGFJm7LVTXWXQ?ib20h zY9;#zCDwl$Oj6#8lcq-v#-*LCW%BweDGul*R~A*GfZ1R{>La~r^oe_;Nvoq?jAc(uWz%J>bWh2 zFL3dnB_p?@=heK5BCQ-<+va;MeSiv86ryWXJq@fl;sNTDl?Q+eJ2bgp>m_Z*?9!0e zdf#8i5B`Bb`0?}_!=q{?i+=tESvfg5=5Yd3CMUPqxN4*Hz-<$Ao*}*paV*WgO~px- zX{XV47e2q`X8RGxwNIU#hI|RsBypqb=l%9Z!%KiBlz4wltF#RSqL(T&S_^XlRI z;a$Bnlaq=)*3`{Y{2%bBNSP5~ldoA3rWVa+&-n_&Gvgj_<ogM&m9NyJm6Q)-!CSuh^ldP6AoC zMot1z$7X>$@-Mb$Kdx_#jO|YRJa&SpN!&ZCFOW{9YPNkpGa`3c(+g6`4Ip7QFzQYw+a{%3z)HG=P=L;k5E-c>}Z$8r~bw|@wJY6Z_Rv^F(D zGw`(x-{NMkcQ6apVJ#b$fUx^AI*mfiB?@a2p`l0R@?ZPv!+JnpYp7?gh*r}t%9Zz1 zr|zQ+*R}h_D!p%;#;-BHvM*0ID4T@N3z91J=EAHy5_qLJG>uLI z@VLy!K7>K-oP}wXU(-zT}XqUsdozOWeZr6H9&2%YY`gxvm#&|%rvwfV5V-(q@w)A@Q8`Jd3!r(g~txrSzHn$@zQGd zgJ(M-tOV`)3<(mYScQ-#J*6XJ_VK0<6pm_Ru)7haYpp9N&2*uV9hzg2Qin>GO)gIj zJtbqwL!KXM`VCw;{1-F z3&^VS;xvZU(69z1PrTC;|Ds?6PIWVX*{FpSaLVFh2gz1VzHKE{H?@&EHr%nLfBlR zOeqj-cwk`2PJ+*b;hzLs?N?V>M=zJICJO#Zhr9~3{Tlzz->!W<-B1`C?O0O1uBen) zRLiQI(aYx=Q<(HvA$ZtP3;0SwpAJIelXk z4y2=yL*@`D$M6Jzo+NeHWV9{YoD*kRoDK)#I}W&H8aHYvgJrl|{k5ej%|$lvM7xo^dXg zhAMZ@sQu}65t1dKQi7!RSvS~iuO7hOAkGtyVt>mNxRE{!&KmzhzPxih%UjM5_Hat+ zrlMvix{!DdPhSfcmnKt?LU!hJRJb~jq7s+4D+6Pe)G%qMq9FikRq2qtPJ%v}z15i& zk;2$25W9uSl!low3%~$U?on{V76gHn7n0j71Rzx&kx$VsxMeQrP=dASZ1|Dgzdc_r zXcdgu9PP9nPd#nAd3o|`)IA@$8zJAKHB;Di6ohFsH}nj9$~bvRa$+G1srG@s2b1qe zVT5np%1h+xz7~Z%uhrm${GFH6TG{SxE@a3a_%sDQU6IW1B|%tdK3~fANPzDg76#BK zC&oM>X103ci&lEEwDH!oE@Im{7+X!??GJCFZ^4^gS>Dlgl#9@a&ULl`8u8IpxT0f% z|Hao3@zj`1UYZ6&D?SOk=XNI&;@LFtvC~Smj55e450jAPa(_b{Q_3RId!#7#O6^T$ zAYE>xnuLV!v^41IE#QBWHUBx)qLf#EBe7p8U+qugQ}~+-sRX>{RIV!~C_ntI6=B|{ zlPFps|Fl(n_KOvNn$=ZcCCQ$>+kZDfWs^bWuRhaSuO0D@P#$zJxxm<_UH&#zA4_QG z=l?s#&#k&zz>kZfzTGEsz@W0R8KeDZulTL%Ve8|X70aM)8IL9H95hNvogKBzHO{82 zGe+xRjgYY)oyaY|ztNddVq%q}fHC02CBJnZV#wbEa*VEIzq&hftlQ^);8#{T*+zda z2z!6NZ|__jQOQ5|#j}C&CX_QgWzA$wp&XClIZiA=zs!mWc5l22Hr{`!NcqOyY@lva z5hd18zXI#57c|%{%bBppny7RxQ6f&|u>+G%KcBWR`}`la-a4+y_w5_^VcGh8PmUYa^ z07!=t8^n_`YxZ%386MGsR(rQWQp@e$f`^ITAnwB>1VszntuNLK{-Y!QA zWKxqN2=t7dHHKBPJ(1hZ)A!h?fKLrJ43+})qk#0Mz459^gM(&9wPJKk&N%-l{4-ON z)5F8(J2*Q?(w|3^9@J|4&|yssmT{YSon}COUOCKOcG|fdedV@%`53f`ecQv+$Gn2F z_~keLmKSS#7Tf4T0n6Dxf7+sPsu$N9>E~(^BVO5~m09hr>M(&bDA}kx-?}YTR*Lie zt>qQz=lk8*pX2LAL&_r>UsL}+FDIQ#icT$^0te})!seB&G8i1?=ny&sb~ooeEyU?o z)~+GFtup18GRi}@9D3(s2L5r_tGm8ft;}Mim*Xn=D~#C2Xqf3~r;O!XI-10*7Cg=-h z!Agd*SWla_5B%b`{O2Co{TE3sZ7<8|f`Si(qgHY&xL5B%bUolXWDhI)SxZwY6c{q> zem1=Rpf0;{SO}Tm>(j#QgtcztI(55d;+c5a%Yge}oq9!?@>nZ6>ZSIpTTwZ(b5WUV za|(8n1EVz+-pgzM&edLVF)qzdHHRL}u3#B+EnZWn3sj&Rl>1)ak_vU&P4kt5Y}DK> zO*^RhNo1Nf6?}SegWpK7wosIGK#XpS?t*y3%kCwqn&ws`rT51gMRWngX;WmCf3J0V z2bKG&4s%N`s_vtRw0H7)y1Q$u<;>(CcjwbQ_vuRGQep9obmd@#pBk^I#SA^ckMsr4 zUOhO_6ZuFkDoHb9#nQoOGACP4^TAlVmPac`lNbL1C(zDsceVgs{ePVvmJpDZ{;l}{ zT6y)4iNh<`a>8#_-5^ig_dXb!pWmC7JOoGh>c+HOe%8z;6E~Z=sN^o())KM1$YAlZr6sGa;Vr2$LbN6TYDzdVx_w9mxdJq1e2aM=j;~B+ zz11P0R|^a-t%y2Ll|F2-F{Av)%`7XIAW}D)kG9-&SggRs>^L(hidR}#?_(D)kUTwfO_-J+FiAB zwKW0$`VD_D_3S}wd8g@>{g<3n*Y>{0w`bS}fv7g!;^5x|%t-rj$=^+#hbUOhok)>(ooK-rljzF2qzyI!vwe&$)Q(MOWoJd#s-|i?90lI^;M&W@jHWhnXDD@z*=kxgb7U;Aa+1 z-RGoG@q|JC_4>Z;dWyw26Dl!06Chq#?|}eB5R#=L+GorO9){!z2xry=e?BkTbkUp_m%;lPM#V|rGI@OF)-b1 zG}x-tbD}|MoO9z@pg~IPFbD}Qu~0F}k_B^4BUC*Nr11ctaDzkP==lksTK91f-GVGb zSQyMyyxqdUw}E%;*!b1v2PpCEo)(}Ia0mYQuM_O-ij_i*dE9MJS%?X>>{F@cI>9ow zh9@Ec%CoY)D>I>Ch`7nTgr+!oK{|hjmH;no zQ}Si9ksy?^C3l&A&#Zj2<+ZsXE%h(+`s=YmLV@*E~hdSXM-{ZAz+2qk`ZKkw2m1hh+h#lJx?X_wOvN2t*?>;Y@W58Juu8K{NUYw>AR z`d&#^9j%HQF7d%m!uXv$A?^qY?!*6M?J)PG=ZL|ul)~xzB@4mSFr2w~_H|%25)Gsw zc_+OKc$0nvFMF5eui)SBogylSmJrN*)y7ObvhAT*Z3`;T=qE*~bev%fjh26#sU=~Z zJ(6{(FUHP*3^Fa+82d#fPh|+6IGb2V(>(bJaUGlVoFCgjI~jU0Y5mDV7jTQ>_=L>w zkh*VMW~^~#miK{|%gL<>qIW2hryNop|2VH3XE|n*r=QRxmg>+aiH4JqLHncVNJmtD z6^7{w;wHS6PT|K@L~OB%@vNmH0F$oMIcsLcC!^1%pF4C5S_r|KpqbO0Tn5O=i9k^Y3M-0@)B z+D@BRz`#F09G@K4n2H9q2d#_&uOq1^I;}lI0r*9woXtunA6<{9Z)Sg&B()JRXCG92 zb!Fx7>1zAg;0myl@)LM6S+W1Ri%(8Ej^t_l7!4mzzmp-hov)T`0H)!0vl8>9rmPC3 zEYT8QUEiK3mgd4Bg>-ZH+ zVa3*sWk{a`BLE~}cH@JP97Fe5vu*O_?)S>UPnw#)h*ZA|dnCic#~4`FQza*~yxQtYTR)9{4&XOuZ)J zh^gz_;|AL9Z%;V==o%0GZH#$(P_}igSrs|%&B%KdR{76~MyqXai3j4Er)?|ufosV~ z5(usFO#Ocv$#-f@4)2>5IDP-@i$AXZ{p*`9E50I7OcicFs-#U(659~`)vD*xpl5m~ zv45M@2LATj{^@B(^V%0=7_BOz=eltdx>3b|lHVk2I7glJaV@nXA~1C5Iao1iJm%W< zerw|u&$6Ry-8%EpJZuTGU#Y!T>Q*H}$(!n&`k>%w)rVDhgAIvLYye?!^F9tNfWMa- zkcWOjeNnnqK6GyY-{*MCVmR*lFX6RTioO2t&)4Z-CRl?>6s!E4wC`qeE;jgFERB>* z;G8YRV3Vd;+~GjotS()Hv~e>k%h2ESY%WaJpU8Aj(Yfu^l-p#Xr)TYIM-K9p(U|RPiWg0k7o4 znka}%T5a(&BJGXLaK*ydqPv3Fr0~<{ly%D;x1dIMoeUkC3ceXXUeL4FjM%kP0(8hs zjBFuH(7;A7_KS6t%g?R4!?a)-3w}5HN}38GIjebKP`Pb0wZ}oDdK3cjvR4fvywz4S z1l^q-QFa}S`#hYtPL)M$V+cfK8n~{xiYWD{DyRz8M{l-rTJb$3xcJBa(t9m_z6j@Ax@E)WjBg?$Y4x1z<*MAE!!EUY|fj3^r7V<6OUSN(_W_pbp*1j0E{Vx4| z!L+SY<~a4{8Ad-9=tv~j+y?&kpQjQqKK}9pAaHw9#>vd1beVR^z%VbzlM6}V12Lg% zV&iC5%3kPtw|3Lb1J*lnmKiKAz01w;))l8#{5A)k%aViMv7ZVmZkzQQ z8gFJoqqA5rnfTdkMM924OT3zbp<08XcCvaf7RT1!K)3*y2=#uLCv|hGSj{|L(NDcI zeZ!dH&YJnWa&?5s``r=>~YiIDP-z%cZl-fpXi(7wUotNX2AC2vHXdc!t|ujR^_{W~Xr zXwaGGIZ*63Xdw6MKZ_1T9#gQ=MfdMynFXu)1stQvnUbw>wDhzLWD;h?hKx|1cU6FN z_M4krrlc%U3&-#`aXR99pSrn{tsl|_5-wJ4wf0|HRF%CJ1h}22wM$jtjrD%PkOk@w z|Djq9^{Q%&39x<`cHJtbX=LAlu7}XBt$VE-jmBG8%9M3tNk|}Qw*K~Kx5J6^iu=?w|?Bt;7S@!nKv%(uxS+PmBEA5u0TxOMIrpY-4@A;JNZ2Hu!yj7`^ zo2g6s@tX-1$)H=tH3SYssAJ*B;Ff9Wn+mWgKGvTjutEx@aTOoH?_x_hX?-momd6Fc zMYs!_W zC^q9eXRn}s&|m2L0>bk4+IHVulCS5FYWzY13=~OeAwDr$O6J`_C`*5^tXEg6kCwMKH^2cH8C4tju^62RV zx8?0i#mhkJToV^u>HuEpqtNiH^8eag@v|L3TvR^tLNi$&A5M(z17VG}d2A4*3D_do zCR>G%CIgw_jhxmDsNPI#-U-QK&$X8BBLJR8U`2GSqwbky{9t!Cg`N6B_gkc!!IfML zm{~6&t7am5L3)34{hVjEo4~O4fQkVhrF}{Cj;rB6f~ufkE#5bMHpAk|s*jz>9SZ?u z>R=t)-N{c9K63H=4oB<3LiR{uOM68n5PJg!C?pSbE zr^55N4Q-_qWN+;JMmNz5A-II!ZT;}VI-@s? z%pmJp$^vE*uHs2H7dwtn%?<+F8L;lCKrAg!ELI1fqAQYX?Ql!76cS|7+ zz64R-Si|hJY=4iT5H70)jd``K(2b_Kk<%Q)Lv}!?M3oJdej8Y{ZvaeBlcTTd4d^!2 z3{PIzL=x@?`3UK?s-$1+QRXo*tjeb=62aZSj=gMQ_=Po}#JMB96_5OFb7EAtlG`7( zb|2N}7X1)p)m@y_Gvl!CA8}D6_G3-{ed$NUh}KHB>}9`Ob?Ho@A7qQi?&YGiN6O8% zdUmKbTSvF!Su0`XTXao($q6Wqn8t#2!O2D*W4Wsz4U`jtt+R(I;&V}ATHJNQ2QP^a zSnbB|Lx=m9(PAhp_&E$Iek8Stp-{4^E${Si9JP5oUiLL$-C57_66JQIlz8@J ztnJ9ZM3aZH5HlhhTM23q_q@Af;vl^?6xzGzG&2AYdM!ZQ-&p*GeBRj8%WpzP1c{*n{TJnH)?1(P z6)_9Ej-vZ~W=H_|q|7HmU|As`7ac@1B&G!+T~J)n($2J@K=~TR2k9CO%Xs2utZl9L z$@FQN4`9E-PHk|b^%&XzrdlALujL4lgw?(6sb1sL$|47W`)h~U(yHA46>iJfj*9bJ z)&Az@d4$bKZh*N{V!w2RtE+Kh5k7zV8y@HA@k6QgrcnLFaMY03`5%bD<7de}dHKp} zOqJADZaLq|qg_hg!VTPN^DdYouE<$%-bjQnMAVsfuR;$t72i$Y$dNCPO4^lRnEZk$ z0o|qn1aB=`2kw*StvCxVZh-WzJiLp-e=iUB<d>rf%f zSU3vWX6<|0mMZhx*I_#8qJsVEH=yF7X?Eo;YX8zpJulBIN3ZA5P0V(8bKl={4Ieowm)*(G zqP1iuOond@qZm?oHKwOlqZXQ^Zky0cezO+_W3QrcvQliZxdzuEVRUuk$2Pnxso;-g zhe-};vfdSs(AhMjr#QeyC`QnIfKYpp3({3ZMx1r4}Rtk z03KTEsc^RJ8qwz~c(^SgYpczcbPet(l>8BITkPNyr4r&n3^o-Q8NB13hA!L*hX4bo zCzY$%_p#Q(<{e#VI(EjJ3+iw28UKOqYo}E0<7}#F8b%vcXxA|Tz-&6bZ6?fdHv_2@Tj8zaNV@pPI2+<}oJc3tgo!NEmz-*l79{`3&nQhU|i>> z;!y;jUPUzScVYZZrFuIL{hH?ltJ(-b&(`8lgnBFmi!KE3uIYm&oph866IA&Z@g`!A z=xVjbq9^}JH5m&^PCZgaT7tPP%&Z_}8K`QuoA)PwyEX=-`D+z^IG{ zv}JGIByyiZ*V_XhQY)(V98plb9Tbb@qUlhG-WmHwjpIV&#bl_n3xtUWp{LvVrSna8N-`ip9(qqMu+A4US%_UtqK`x0Hk z>Zg~PnJMJ(Pt?wb96p?FD@$-1bP{c^%8@X=`T=hM+7f&2ZjS6T6eeLZpZMsB zkvoXT7yApeNTz}J+j%2##V1-6)_$hzxK6*~CIBmc(+n)@|GVNpXgT?eWB`xMIHk4x zWd~YXZ!4KLTYDAN5taxR!c$zsuFWo|Vsc|YF9s}yQ>*Y;rXKer0Dv|Vy z>8aR}l6Vb+2-~>tS@lfxF1(J`dachC5#syXI{_rvpSBWnfT)JHYCht56yM6nLHx4{Q=o$!+#4gkYcg z%;_Rw%R?I())Y_Bm$`!|Hw;7wS$P4+{ zL;Y&Za|DgpKZ4c3HvOcCP{_X7VD4=C#Imq#!(EN~`_|1G4CR`?M!ww%E{;sa(!kte zkqv}1k-_zRC2f<(wKFd#bH#jt49z#Ze^GN+DpCCUY&78aXXD9Fp4*_;eG@R&j_Chg zE+5*Ok$y@YHOIqS$0>M$hH{uu&Y-k8#Zhh31#sf9dqcga9>>S(Ob1Htn^@Jaep;)K zl&cuF*7!1PBtgs<8SFD)+1}gU3E>emgs?`{y^~J?u!Swv2kt+X~ zu|qQiRau*yUS_{ox5+ZJWujdYUrMJ7Pph6>Lf0+(E)H{raD8^e`b9=~o$Op!;uz8T zeFE%1@TX@~0hiNmQN29gM~T%do^|Yn6*yCAk`|^e=(w9544%~b!aMRLh23e5h=WzT zA2)HwDK%X6YZ+sEMFU<`4BSbtrlm65_*E?Tj++E;qRhonW zyYFR~Z*K~Y=t56kz}i^>-^c9jzciuzeM3kFffo~$P0Kq~=l=CS{WQ|Y@$l!$d-Crf zOq7hM!CUkye{|AtsnWJ+IPS1%GG|pef%~%-lrmFc`pk*##g3K#UZ5sdEMkz6ZFb;$ zIQ0!IWkLTTE<^gTf32eZW_S^C2U-U9$Kqs!2j#a~op}LgOSAUpoO3E*w?OBi5a~N5 zJ7U=(x7cQ}k^Aj#oPgJ^a@P#tT8Zm2+-aKV#$5+ya>@3)Uu%b1(PJ!hE#Vh)zX>%x zNF2#{b?fV=@ITujyOW1j{&U0uz`g!c&hH#aT!5t4<-=VkH-JYrI<$xC93c|KAXS}J zixT}WPcnsAF5dH%E~>Z9kE8!b5k4&^T|=+N&2yVq0EqOE345!<#$dl9Ts+rYx*)!h z660R!8Dvbx_8PU_d>#0O%G{~7hpXqrU`sc_>esKJZ0M{j9)u&@8YxJO@ElIhQXLx`xdnou)`@e^y_;!s=-vzHSzqte@xKR@i@MoY z-Fg=mj~N1zJ=dYitS>8Q`o{^qe$4i%Xf{&`i-quVse#McTpLDwzYU2T5<3jJ`Uvl0;Hs2l(QEB}R|;7D`EHqKx;t)rD8px5xu4nb`wWxoiWk-XxD zHC0hVZ|)wO*R~tz?;5qSam(dsn9p3tvzO}&*sbz&JM|vz=FCiFyJ1eX{3^uxT-e>$ zA17-HX&;6U1$Fluql}r8^w+IQw6m`{?bR^{{yD=@>RXB;ei9#?V9PMEg-IHV>Px_X z_ASwh=C>=%nS{aSRo^Xz->Xr8>gVTtJN#CsC83_$%*S~)EHbOVoA#Y`nyuR2TN<@Rr0AHj(qadt+u_S zkXpaG$1-UEx*3A29&nv0*wHs8KgboK9lS&qF=JC5Y}{9FIM_mmJx&yA^wQ6e(J&8f z3siZ34aq-Xnfng_rpe7r9rmwo?T?;cP>$nJ1$=TiNuO8BSW;DlD+se3ik_>-PVEFa zMv~!6`P@6EUZ*AvP<>4Kh^T=o+;mcnDgB`Ch!9Ie0Ipm!d2@=&6n4f=)6nWZHYBvw z_o%6=bXH6+N-JC9Xg)}_{Qk`0F`aia+3O^jfP-g*9f5Ofd{1^7!{1v6z-^ryX4?Fd zBVIBCRAu@^u#G~P=M+4^{Dr1tEJCQ;@sfet=)Rj?dApn-S%R+DFb-^0Y{bY1uBUmz zi#h@?cnblU@-kZ6HK}186H#Sg0uNBnQs%!%r)LumV;b!9zXHKhIgLn47L$dzU$5d| zud9aJ0j*1x;KdMoi4q%f9{7={9iPd-HPq_nqE*rb@rLb54upC*Moku6`YA?nl_m?e zGnBMB8P7HNJoYLFwk69l>oT_~x1C6w&@WdP(){|I7{&M4{jBDJriP*GR{Vm2R8ew` zJIk2f=1)0DKz+7$Lk=b#%l;Kj{vXSf7H5G!g7Z5hxyGNZ2TS3-q_`f>%>z|!8~$9M zXDBj>Ov;yP$k9C7eq6XUl>@6erDPahRllsYp={Zh3Gq$9j==k@vZbCR_c;R zR>WIglhNQ=-G0B=(jvvdRFErUA7|XZrZYl2|J@m$dt-PzF3>u4h_yJDvBigalND69 z?xI$-1FRN}XTr#&VPnswr;#la4O70rVow=S&L`)c>DuHNSk7fRA^ZF776ARu>Hbo? z{SCf;kx|PQXGG9a8x#0|77r8MZhvtlwf?a9dEm%rMN)QBisiiJeJbtig!PFLs3!u~ z9li`xtnT-I9Mtl4w8v1x7e#Mp7wBAm8!0bAYEs^i$`1rWT7I|dQqO&jx=AMJ4TueI zb42g89C*uV^{KB^h<&r|No&R+Nj5K2b&13&a&N4|n8=mw;Q;6O_S%cOGO~)9#Jy*# z*lY(>YB&>e=X&pmTsnOYNua|ZT)Ta&Thx-8N-y!AEttFR*q4Wz^2&}@Nzh?pS-0Or z-Kyx!xUAYc%I+cgx%XwE0rl9{N7PrAEefGgcxbiB&KsbXMWKJ`BAgU!Q=l}jtWI;t z`84JqjNa|gH8k`}{^Rg4kjh#f%s~Y8|C!2`#YDH(^VZbgNMaYmoL@(_d3x=(Hv=<> zl>vq2-C|30FW}fyL@W8Y$wu0l0Z*(u92QBe6MT!Yset7NJ@}&aaAV-RcY?X+X~@fe zoPH(g1Z6(COWk|cM`NbEgJdO<;d*7Y2itm+bBlmz*GNBG;|}GrdIbNpO`1qYTZlq` z=7W6DKWavM+ik!lv(}ngn&D2Kk4DY?+V3BE6gAXro}8L=7b=!lwRmLwaw*m`f{b33 zPQ`#wg#?^aG+7EqDbnebnezI2-(c!UfNrD7&YYAPNUBNG6c}OGSb{@ z2YApj&P0b;u-PmKI%K}MF!lkz<4VV1yqV-9rW=%^#5_WsT=*Cfu}?bFqU`+zKbhiC zXMI}6xvyukBhUh!q?I-RcTzhRlsCsUs8VLQQ}7PT3if_pUTI^gX2(uN(9Ri-sv`H$ z#DVDlmGJ z>^@697;$%oCld~BwAj3e6ZgfrlO9_@8}5D4>Q~z1;3;IWI?1akD1&0-3)wlK>mn%G zV<&^jKXaOVQnATmP#EIZH$8F7j8fj5$ojrkfNgG_y}pwVQ{F$65sZ#Q7-e1ub^qBt zb_WwBubKCqZI`7uInAR$u2dMSIFzOF?9!RH#o3mo30!$`X1l{r%{g7;mObHP*)TjH zc&-DXy&8pA<`cfrP`87$LBLMV=058ye;fb03tv1DZ&;;$MDTcsa{m1BURd2zXa+2O z!|+inS@ewRhUp=77M*_LEoazj7H)DL%puS#Tk&%sVpbHZ5C8m|vXuTnFbJYP>Jqv% z!QLyp>fZA8&P7q~$du}MtyuT_d&Mycc{JT=&)usvj$hG9=UQDYi||;cfS0k;grJ^89#lh8;7b6P zpoSvOp}dvTj4Nl0Usf$XF~HsgpQW!^PgoXg-o&r_;}ExQyK^f6PvK5hi^I3(>M+yG2 zP9%DC7Ox#5FT;E~AQq<+K@ys&?{=ci6~oYd;s8^O62F~Dug3#WKlixRfy5?xb4J&c z;V^j~eBR`i@}Z}teO76IFX}Z>QQq`p@~-Ysv6Jex{)jyw&j%1MSu~_yn$=vgpRt+R zsn36>3qhv&GYoE2!0ck%**;t?Lf`6AzI8Vqrydi-oUK@ShUfp~3glOs{6?_oWlZaUnqs)8OzNCxh;4oc(i+Wh%SV}8i9`PE;Of`2?< zGuE^b$g|-$dGz)m$bC>K)~G)N=%`m08-#t zVSgMuFBl(~-yHpFH1L;~4inI^jXEp1?eURydt*ph3KEeejgTNuR zPhKhB!RojK_2mz=lF`S_vFpIaJINq}aIz}3X)$ImcSS=~yRK!scWcP|boV4kLcF-h z@oMe&BGM5Wu_pj~*)hEhu#l>pNbv^h_n)US|7x(3>CAxc55ddMv_$;lvu_e~;eGMl0lY9njccryHaM8?^P@5e z9KKhr@#YmKKM{WLB%WBOHP>j3)2Lrr1Vysg*9Ns7j0XV8?IW%xJog<5`{eng&4Uap!>{cHBzeIsIlq~rloF5Xd(_L`6YVL|>x4T`}ZP?x0NtR7?7)9Sf3w?XN zXqBtk930we$K0drR35sp} zh4eRxs<&IKXjxF70NZ}7Z(4L!6@zGK|7b(bBEGZd*PA?aRbpnIx8eQ$07$Egypzhe z&b-CIajA&|BAC};i_fxP?W!Xx_8UvySFeZqMLG;RoUoUa@dfqNYh280Bh_jSurJzt zIZ?n}IB9rRHz9QBzz&8y@RBLLPH=Z4^>KaUiaF4Le+^;+VCYmdisq3xkWRLC-|3D* z>e0bbY(s%9TzhFs-s=23J3_5o!bdK&Rx)z{gq~=99+9AQVhA9z=d}N#RGGRcFQOTj zF(&Z&aFz*%R&Fp(t!vSV?^)U-0I>_u9Q46<`s?qz@)6SFd3qkme#KAJKWbd}^6M1f{*sW1*_6{`?>p`|o!>?;Y&;JJJ2h5c zc&E#Lv8l0~_O^~W;J@f>$P$t_eEHP?W&E4o2k|i~?_aT^qCa_?L;5LYzX?LXj^1+n z@abxIoWnKVE-#6S$O&gBdwsnHj{Gukm`y+H+Mu)ZLU50)Ncd3#bdX-H=ALQT=k*!y zTGjbcgnie_GTWZSNoWhukG%LFDcyg3rtdrpARdla@BeGg+vr<6Ri~>xV;_K-Wo{&6 zPUxCO!8Mvn4JQ;ysbn~3;eH{hly?Y)t##|N+ zzZ2`ai%0_&*5A=5Hr2omH4WZ$z!+BdqgA3!Bl>>bpn+dY7Tu|LsNfA$4(h+Co2D5)*8T*G6CGl66iL2NM_LgZ4T%4 zE&O?h3OnoVXC~#4k+&aC@@@!kMq3Q9JsS^;TV|JE+K)5A&<7HFQJS9F%OYN&Su1Qz zIk^=7;xQ?ws#Rh%Ji(`a$zPlDy}ET1xJ1O5`yRrkq4k_n*!OHSNP@@>%ItMFJx z5i;4Er&$MdY4fBFjqg;?xWrw2D$#J5C`VbvkIPD4{j_b8=R&<>;3~c~y9oy#+BYSC zv1F}$KS#`L7;`E+SHr)`5ASn}$JQK_GN>(mH$Ik1Tv_?FJ@c70$de;|oi+bLI7C2u za)Z`zqsj1a4|>!qXk^Ed-&u9ru4gNL)TXHQ_YMMojwz5r)S^`I&C*R)?fSo_E&D84l->t?+0cpYM4#b_RP zL>wucD{d!(Ys(%x)66(1q3->%bddn%WvtD8lG%~1ua2lm?5y7PrnNwjCn>7SIFgmc zw%r*>Uj8zwU+Px)OdHq;I1g<%Y`NkRF!2_d!^qiG|MB6T{UBsFdaYw=VRvU7P6kbT zVaEj<%3AKy;(=x=edX1!@Yg1osr5_kCU^GYREsK3QLhBuC`@b0QAwujrCHe(=-zfn zFH<17?L_l!5m?-TItT-E&8e+-rGPinW?oA&Oj+P}Ziq|Z!IW@ofjqV#lAZ!j^{gdTX|zS1@2p|a7q&X$YH+XFTu<8t=>nBpIGxKN>`Rrq07_kS zh~-bx+v|d9ap7&u?yh7R!RBRevXu9qwNBE>(#8e=S`NYIf9t-P@vl$@2Qo}tmN*fn zMbpN!GjreWJNh@9%mpdwaKxRdo=13lT#hcRU_*<~wU^+sx_&ywcZU`$8|DXjIc{6~WJTi;Bd`pf z2fVv;MZ#OiCdn|X35Cg@H6E<89OkQY>-%2?GCuS0Vgpf~0G78q#>mQDF096#+tZPVMy^~P!J1&LM-5a`Q>pY|Nnjs>+MtJV?EWKg|( z|3`w&GcEorF4U`g)gg5^S1;Wu1?_LJMSqxz{Fv@o&of!5t|O+XB)yjZ7$(^(^U zf7npq8h|cBt>f?;`@i+=V>TcU?Mk+R=*Oz+j0Ezqu{g}GYT=)k_*i<=^hnxAf>z2K z&i*-piw!&Y4vj$+q|Y!PXW@*j-FeM#2U+d&H}yL@EDCa*lmdJq-$*_C%bY((( zN*EB$$8Ur?eV<%daLaFbN!@XPYZF5dqLIX%G|SS@F{Al?LSx22SY41H-?*ucofSSU z?ACOFqJ{$dFRs+`6O$Mr1;1CxA33y}f3Ftx>)Xrh zny&s*^yFbHu!w5g-Y6dvTE9RUx)t~pos<0`n`|A)#3|A1{b&SimP|j`!WmEX`-sY? z9YoAsGYHhg?Zj~t;@YxwLd{fXt4|iR=Kq!TtoSV=LmR%h_c8A7_fC-u~AoMFB@<|Gf&;;;C}%r1ki$ zpc|9ny?yGw%MS@ordaI{`UkC)kHfN^mIQdZ;=`QB9TW zo_9JOCF;ACjdX?T;a;?9Jp-3BtngnjHF;G)U7QvK1gxrEuEx7pX%)%Rtg#X+k^v)g zbc}-`<$enV#N~dzT{=*iwel)d9Av_5cLzw+-on4okom+dTT|wsA)k>f!q5^?qqER| zm*`fr>r{`X5ub?u&3*OPsCA1%UXeBUm6^aCSDK^|NopEeqeQXA+W4Z_ ztWx$WvlIVjA_K@Zai-`9uWOoeiDc|y*`DeC7U&UvwhINq;iofoj-MRGx#c9>w*LfG z)S+4+qXXFt=?@y1h%UONvTcP8tq$>3-zy|iwA_}@5K^6Bv@ZE9JiYS%-=5|n$HPmd zCXTkW%h$rps85?BTMoC-vtB=)v87@{tM(7lP<21;_~*^H)Xg>H+xF+Py}O+u0e*?M zO4+4?Tm)vz^AoZhN8`gmf?9DYIei)*%b!67(zD$G9!Sevx!%_1ks;Usbn4z*Sq~%! zeY`d8HhgT$!>&HSDKpSp71w;E&ipp{3SZHGRo1)S4|)$Yhr2jL;`iDh?}jq zoqqEH#0{@E+&vvBc}^^1kw~0cuVo=hyalAs;?BNqbL^D!O6S8szNz6D!)N8Q-iJXL z(rFaKT=5w1p4SNQ{_T)qDJ=;!`3J-U+0dUUDf-TU%%2VCF>088kdvJbGK9Kq&{;#m z^ieborS>;ZoY7YJR4XHYmC0~mBQ`7Ujo|sZ8HJ07pI?-4j2KiXDXCsnR~)bH^ZAGE zD#yv1aSbA?4q@V8YJIfeP9SF4WV9lPvb&9>Q%(cpuirjQ@~6Yd8XF_a)p>Ym&XB3t z@uO5n{ni|k(w2Jj3z71mpLqcgDFUl+E?u9Y#L>w~%@`dO{Ic&1l{A%6qj}zhfG?Pw zp1a@uMjhn%y7u(6ax)-tKc4DMh)ginvsz4bmKN-SomPDk8$B_9<3z;1 z`sEXegBUnH*?igPz!TE=-K^WkrNiI#7-zXasz&v`9$7bV_R8#jx`JQbChtRXpS~Tz zo98ZW?H|#~=61oT&Ix5+{l6aiPwCXVb~VQ9l8I_*9tCN=^cj6=J<8$thMOJLie{5a zJ(|s@N*FVxkL+UjsEi|Qng&f$;L#OX=|-P9n@b&^es^rH?l*1^0Pp!$d~E#kOY?*U z0itVp?C6JdN@SUOV|R|fnYp(y+)K1|(tx5VA>>>dzj!m0t=luud11so!_*pPZ0De# z>*R2V5C13RR@#;DC1GdK*NFg$L_3k6Ra=KTcXl^FMtwBq!M$n5A=hQ98}O}+R=AZE zTt4+e`uCsN&c$|teUH09%U!e=!?vbM+lq7@jd>Wy3rvn4h zxs!t4H~$f;q4@s`5USC2?ggoHHp*c*@6p{A__XDdP*>t}B58i{$IlNk4ncNGC5u&I z_7)C)7#_3V%HMLmOw{4b;W#IiQMmn_!`iQ4BcuCd#jN7{;@?Ee-@4Vb}@~j0VBw(X;{SrRFAEAH2 z0y1rNr>w&H{zlW6hm~~=2r=oz(i-d3R5~2`{0q?Rg8gUS`T@r|N&0)XhtjJbQk1gy zuSLHK*D$2{HeIJJGbG$NyM)c@Jr-wWzUK%!&=nihpZS69&+2=&>RjC1z)K#8m8E-* z$p_G9t(WobocZsk28CCe9;>TS$u}#(iPh~C+dT~~v>_em-mP8yzqyxR6*yk`@DfC} zfAlGZI8jCPWH)#P-<+=x^hNH%mlmB`8Y)5S*lmIWvJ}z_w95WOoWk*RbR7HwU3t+b zhn%$Gf!(TbgppuAm3t(nJx4iNQ#3eY$duvrXTjDAL){1ZeOF#p&wBNyo6rl$Y{!gx zic+%WkBQP}m!h_4z7HDN`S3cGs}>H1mSeY#?7rrg9)g-PPVxQqp6nMz+SyR~i55Dw zgBBB2?H3YnsRMi%7=5J6ctlG3O~A(nKISM?bP!;jc4z?6Z3cX4OgSxvokaBAm)r2~ zY^lZUTYJkf(%o+yr#&wHw*J-Bdu5GY1}!xmJh9Hi<@bqBwB2jdMc|ye&iS)$^xrf5 z513|<-QhVl*fHE~F|_yC%~kA23>BiotGsCTpH`1~VZNgmt5c<>vh+hur;iA~^98?O zjQVuByG~}i<&QDLIfK!+sVA%AhNkq=VP4BhhEk=4e?|++roEuWf&rXRB9oB38;?r1Pk@g;qn-X@$>Vve z!mt@%=Vjx8^=SIP>P0~`n?sVmvtzKF$ ze(J_zq7IieY=LNfz@xJ}A$AH_#mVxO)z-CfsSg~K%h?~NL`g4KTC~k00-wDbnN00q zb;bzI25awfsv9fxsg$@{2_O}bIveVr4)0vDo>rUuaC)a7@5ELwJ;k8UvTa!D@X-3G z_@$DVG1`}J$ds5Ngg!`_z`v9?|2It0u;b0hVFC1q3}%M#Zx`GV8`Mg(HoU9K=i;JL z=ggJI^4Q3Lge%1O#!ScIK2Zmv#>0iUUY#E|5pf^B@B zCRvky8#YkGZB}K7yh|)SV0Gi&@F?b`Lg|(fwmt~Cj(|?LP zIu+gHIhdco_?Fz4zmC-g`P~SNiW!+jtV=cQBKv{{@*MIrBC;CR{@M{6A)zO67oom` zz4xVEYD*8>)g3nTiiD7RWiIWWQFqhUM=9MV_~7fgW0y`UGAeqLG!0c*r*dl5O3b_+ zR`T45w;+%+!+@l%C5GyR$wRdp+ud3mQyMkGxyv19Ixi{KQl+ZpcD#E_vj2l2y48l0 z$n3|$3$&W|Okby~{8i^Vg?M8&Ava>4eAV&tRT&)*n%AsSS=al6`1jhO@$(HeJIN_@ z)kR24HEhw_9&9d=Ey)0Nnc0`ZJVBOgi9BVavaqrO-gkp~!Ri^Cead?ew}de)k@5V( zr!$EoIur~o_&@*b!y4uDWi1Ox6n}E6IKvJdTA~xg zuy!m@`tM41twc5^Lp#+~u!o6tjJ?worv^lZUHyIyhL9xlGw)!6k#%3-feS1w2r+K7 z%|yW~9Go<-D6;wR7uhQGei~X9mDPBxAFlBA@o8SO+Z~RNeF0q7qx+*JIh?<*-!7XX z!~e|Eq3F;jX*cV5JX)x++HLZxREtET4gGKc#gx(Tkshg%<~Q zRG_wmhK(Bemao;gSZnaZz9?icv$)@D9gRe^g$y~4IK+=`*J2hUZTirn}guQ~!NiV5gR0%RFY zofRsrQaK;&&C(m>->>0O*1q?;(04kKm({14T3$4C#&@|uOQT{p(_){VN{7b+7;j{R z#~abHh?v5x5tHiJn&E`YLh2@eTkDu~2E^Po=ZpqgN*WivrZyf&;2{EH4aF6d?7DCks98OrPCeRDN46go zNt+Z~jHckHjQW+}?aav`^zX6%2KC=a*h?%3G zdOKBOH~FLO-2iy!VNDLI3=r^ifGZ7 z>sx7Ug+x0(DPM-YtXC1!jXMG>Oo{n{>=yYYKORV(M;^no>8|0AfrtQPm)VPfl za96i7H*XkzSuc6~+P!EMQ>7SfIhx?ukChdj`z2G~CAHJiwY~34?bfmp1*Hwy6c*&O zzNe-zy(zT3VW2%%oBPsBdjU0B?4~`B+{W8`5&U|fzH64=f&FF2-%F~dcIv?=cn_CG zs?)4%7I?7>sY~zOjO|Bgl`s{5_NGHc^#4{$xl5z+jkF}33DGdq{gen-_Siaxv`ESX zEyqgsV)5*OI@X09t@04r4@F>Ag!-29b1d8qR9VH=X46Y2ONQhuRY%qlaC7^6IpWRS z?ZWYfYw3$S3a0L@G~vkX+^DYR7+nS(_i@;65C?Hfle6BwVENo+-N^dgdCdz?-<$rv zm^9~ja(Mj`SMiuX-8w*nP z<%40dGYQUvTaA2$f=2Hq_Mc2@jvXjEU;mjjm=_DD9^XqR>2lytQ+90MM4M+&iN3@> zRXcgoZ)s%=t;z^L;(M$w<)Es3AmHvtooU%RIcX0&0jB`0Ve8-vdlX@E=)mm#_v~WC zka=Qvy?B1usY zO0}afvG7q|!A8ylzcd6!5gEL2(7P4OLKHl+muym#J9+#$7T)@rbgu5iKnHr9K(Ne5 zhW1ByomB2zKdqq{3B{&&$Yb?_y-a-b1!HzAj*hT)nB^F*@kXAN)z_lsVKgeoaf#;s zsXWjG&SsZo=@6|{$Aaa_fMAT{&^URc2BcdAS`L|y(HImHgOIklUWJ@cKNC2{Ca&QS z|8|R!-RON`zY3hO(nC~DEnPu3<(C<0d(q?_aaal7EtKc9YW#?G+LV6gFY}D^cB#Al zy9%j#eJPblt+E0ayMD>{1H@e+teC-`?v9Dr8Pq+Y>@k&K?H^rkU6v{qsp_sBL(-Ud z!YInZ2#QYW(cZTui*xyR#VR|G90f#PFOZ@0Btw3#I39lxkRbjw!;`OOvIU;o0v zQ#Fn?%2byzE2Gzzi7`+el>c`5hwY)zgn6CD-mK5vTIx|#q8#ex*5VEt{~o(;jPa~2 z_W@h{0FpakH|QQl%Xc~;1ioTtT*P6b+24)_2}*?g+j>Q%=z77vbF63~!?`hIk3{}} z+*T$232UCEUggH>OIuBrDer~tFk7eEg?N~bePR~*pQTZ+%5Lo;o(PXu#A8T1wyow` zro1;9s0h;{dn|Sa7UkMg@V5fBxTqMmu8NxBbmbQFgAv7j;^(9~nM_Y@lYgc~#G;q} z)mRlOFTA%rGj$N>*dIS>(OtlFH7>qZFPfd)7CNAB2eX_w;%@Up{Nc{!=PE63xBpn8 zK|B)@qNC2^UaM7Lb3gBSp!g`3F@YD}_-N&O$&t&(1WV;>-%pjSq6YWouC@9hX`IWA zly*iU%|*Z0?Cz$GbJC3FRFM14Y1CC}{GKp-ldW*r@=lqQ z_mijZ9c^Cit%fT(hw9s;67rW=$>plnVFUq zQRD2d<1B|Nj72zuBo$;@=WQ%pkFie`4(1rE0!|3%{w7GThIc742mv6 zgQM6|!rs`}>D}KGUIrhEcGu?CON`oLPp!5qyLd$gqjQqYA<=)&;v$MNi^um5!(3{y z^v4x0hbLSy{WU-0{ZRT>^QF))g(i1|YZXgQv0vmYgjMTY4yp@M#SOgvJ`)gdIo56K z5GG~dXWr>rn{UI4IFE?c{t)#Wte&2u*4nCJ;u`4OEgN{O{LyGe!U~IupkR_iW%nkj z#2c-wq+`Bly;u>@8k17iCt?U6>$jV5P=HW&T^rN-ao7g>6NG1%4^pS#Vb7 z&U|d%d~!z{s$gd&yKB6_`*#1x`lva7wH!A3umr6yVX%{^*cT}YNn$+rX5p-k_g-n| zbon6{w>h(FKP1K*_4(H-j42H?L~S>x-Fjlehg9x+WVgfYFx_XxaqXsG6P(bm) zc;Z#cPBk(HZ$H|2ia(1*+P>xL6@z`wZoeCEx9knuDjV;QmEQirVnW?^!ZGTX;)rtL zVk))4j6bAdA#oa*gT)0qInXAxyBWp(l14)#AZi`EhOxE1U&J#ndGNW1PrTP)8jEV* z_H|D@m=n-+JF${v#Y9*%k7E2!ud%aFM%qvZE|>P?9-1Hmn`uR5r~)T)Dqae?BzBQa z*Z3Pmm(3@d#}h&3t0e%o{+}yIxgaKk$>Ix@HWgU<#v6?h!qjPvxw#iGE#@e>XV+7-ytmV4*Ojwhrp!YQvHyN%lnid62`S6`ie{T!an^wj z-Kt%lj|%wf1;H*j?`+x=2N|MI z%GP5=ue&ub{f@OWx?)sBcSF$jScNjsmbS}=*(s*_*2UOQQO8@HjmA12oN=`)H+s7h zratZ}4Sm(pVO?q!5KrHn%3nnN9Q?8DB&~jv=u(l|hNhu9lCb$@g&{9tDE(4!hf)j* zEpJX%9{>R)Cb*a{WomN zTV6}v&f&Tedh@ik{cbzVQcqc_65YSLSNfIVu#5G3DxqE7x_m!g0tK=bchY*N^WgQ` z935S+I4RY|UkQOnJhT(S9~ZX3j(gCsR)EkhgM3E&!^xI-eyqcbm>ZgoBk9eYq(wO+ zW$h&e))TE2Yr>9t8~i_K?yxyO@)c}tw3^nooUsBYd&q^#5dWI>&)n-NS6|s2n&>uB zE$TZI8Q`rHxSL7$K?9?zzSrduava_7Q{b1lR}AN7btuGvo4kj4=% z&2}LpN-+;$JX#qGEUc71azDF=7VB!O>_>Z6a&%M_qUXj+637cqhocs~WkNu!J zY=rEL0Gz>V{2<4@zgPG`3}5RVDcGth=n{|jw^ zcoIz8<272vu}>kUhl8og5fg88F}Q3Xbs5`}e?x^a&9irzf0hzuwwg=fRawwsRD9>+n_Qkf zQ`4yleJ3DfnA1uS>vkjhNb2vZuMof<&H$+i?>@?GpQNT|5hv?V`$4Nm>d4t zks0q3>zglBe6+mtg^F6@8$(7-(XwJYq{rv=Mf`+ zi5wzQw)`XYQukfq(_?=&t< z+&w9>rkeifS=&=R;>=BxB<&Y*xwJJhMs$s^SxaS-O_|3c2rkQqvHeZXhkj8IQSbC({&^aW3o zK*GK8{{;an9}tNCl~v+-qws;X)gRibZL!i6h*cD~urp&W8od9zu{~nU96j_JCC5}- zEvBuq{J7f|)AvE+)#_XowMP4u`kMObm|*2WSy>fUCa?8wz^msFeVZ4`c$3D0;c~0VttF4OPCg8NjjuBi zOux_=Ru?niT;1EsH_~}K{MY*N_-A9D;PkGABd%sD2t(wVC)B!$7F%e;hR@U;=#*1! z;0*yG1N{P@k$1>sB!s>w+ZP7=yovPN7zG%{;%4mKHKL`i4}bFhd{M~V65DB+0kGp# zEJN%w32$G|_;TPTJpEhA^C?8VS+5 zu$j>)A~Hadr>XS*lwMph*%=beb%sdV4?OoP(^q^wYODC}@6Rc3+9%~@Mye<2&Qtjp zBk&Uzi?e+$CPnSJOmY*&;XiL8z4gi~Yr1H`wQPVYaB-vM$@@)A^!5IC=xbP4)4@N= zeMtLl9`h!{X7bV}rBFn@+48vm61$GVaWfy)C-%%8xVr6`q)pmsfEV(`l1a*h?Av;^&Fy7ZG>>y^M^f2x~F zwfMlg{R!d@gyJRBJFd|VZSL~HW?TN3TQ z?OO6|7?cya?$U97GuF1YT9`SdFuO6qqB69b(&Kqk#8u&m_In9uDE_;!sG${ zou9`c`7)BUD2i3ne%=TYPGn{9f^Cl6%bqSbrM_WhUO0lv)MT#Me`zpA$I>xQL!@lu zLzM3)vv&!UrmSkLdft(d;}2~B-hmmnc4-FfhFho%Wp=I8?-f5+Od>K0Tr^-OvA?dA zwD$8hk?0IzlW55{x-`ZWw*?3w(G=%|=_i0E5`2#a)&3rWXWh9&=ms+mmAnKM@Lr<_f(|ryhdd}gA z9Wo3r=fNK2PRrf@$S|bujY5&h^p!elR37$}P{)Kb&flq-Q-pc!{g6QJsof}C@ zkHjPX3LQ0|2lPe;b7Ai!1h~g4+&(UyifqG%NHdwu8d-R&X$Ih}%f@QM85VKixfvaJ zuD$$eF8iz%iT;^#7Yj!mi|D#}ayi!fWG&odT_+|Q?;sl?8YU|h-DSYxuV0 zVFt%>iUP;p&bf!0d@lD&hMo%P^zf5uvGVC5k=g7%sIHF>`G&UFcTs&$oBRJJ7BXlb zR9}WLgq+#1Q8LFN zTrg#C=;>+kl)lDI0LWm`N3%S*xJ@kuu{ku&bn~q%-+WlrnpxzCSe=oGYcofld3MgB zCP8O^sjcDaOsw&AcY<{b>uZYf)eU}T<vK)v#yM0ZHee&o8q^ zLA2P0;qh0$T##fYB83`mntWVJg8>o8)b9FKGBQ&Mn!5QGahp)Qk{1%Mw#=b} zPIvjCT?deSKnE6x8Q8Ml5abtt#(Pp(be=zab*G^G|1}^T-ZG*e?+oB2lDH) zN44(?{UHgi>^NWoToCx-Yn<+v|9XiND63TZK^&}c~ioUjf`}$&N9&{5l11Wzxa8X0d8n4o_D}# zIbyPp0bw7Pi|I?EvOt)SA)sxK0wV9uc$VgDM=cm-dI)nkVYZfy`2ZIgf4u(W96XY0>EXH;Im*xQTxElt)pl^1v`*=*~z%N0ncQp-j~&^R)DaCOe$ltI?d?7 z!d#IB{=iI`SecKA{5D*t!*1DsGAB+HgXFs-^(NQ0ee;kEtuc}$&)*#xFBakF9LpDy zei(B(j73gkVDc_IUQKJH4uSJbmkxiqGE#1w(9YqH+K1!jTBXY6*shrS+6&w0J`k=K zawtWM_;+|&OD0ze8vQs5nZ1tPm z%-MToC}fg#KaT*yrFh0>;!4lg7*rW5O4-o6_)#s6x)sGv1=1*o>9%Jol3kyck*2Q< zb!SpyGct;KS<^kUAMxd@8W1OyviFT50mASGDd%vZjKZkR$9I!Ni<|D76-rt{v<1nH zjKD8bHyJWhQTWZ~H`m%Uw^zcDG?=_{_jNty+UVs41@FYY?vRb0f^ZY9&{c+yK#1ymcdJ@5uGNYYA8X+kV@DMV<#FI=A`k4AJ7# z5mrB23k&;tirxy&;=CuSyRHX7&2t-cS?lUrZ2h|4qww+<+-B5E z`GJFUJNjP1M_UrRQIU~j6NnKDnYrB|oUoMAvf zc$<7QE>AxY85JQ8>qg1QF0phxW5d3AEyXj`1h0~Kxx;xis(K+kc?dzG>w7R4dU@_q za(--6h9;BwF2MI?6_K@(W$gI%k4Qw>JHo97a=z~^?o-earOrJXndK;b1%GU;(6OnZ z^|FUA`ZVEss>3sjVphVH(v*2s3Hyb>YXlI;87q$~m`$u$4AM8;t!rA8W=ix1lwUx( z@EK3XGff4H_JD>Ic|7CZ8HpDUGNi-mPURzn!i^ehb5)sAG}U<5r*5%^^7jCeF#CL4u{oo!bSe=}CQH_H?#Z zj9T`jDcFxiwclTB&$V1rjf`0~n5pgl#aF^d)OHcl{`}m`QRd1g+>Tb2li=l&&(}1N z)JvoIQ*n|(l|191iOl8Q?lV!%T~v?JM=NK{unZJY!LyM(VAEo4&a^hBA@R1l!o5bc4*g9kl1Tp8hyKTxOU57-s zoS6%ckS&Dhc>h}>m2`1d8hcZIE1@n4750J{#!35cB#wOQMvj&7?@0#OA5wC(bwwSI zv)Tyba69V{ELCtoF#^c2|Gm7JTDEdf*)wZYrW##aE-t{Jk{c4JCK0=Nji~Y)goR(1 zUB)BflV$?LD#$$Es|@u#H4%P9{=3_MEAb%F?dOKmx%9^S8ZzH@May=VAU*sW-dpu+ zE+mByDd%%c=hRP+E3H)y?B?pLD;?yN-mr37wkg!Pzuy!@NtBX~-i3cwSI25ijD8a{ zlDv?s^io2z!U(mv-hFH^$yWZB>;u60b>p0~`;v2AK7}sOYd1erhd^e|o(_q&ZG9%V zP%gJt#g0q`**A?5%YpVz3C->6oT@5n8gd=$f_6kvH}`6?n9ZM1J9YhuUBh~)RmuUx zghFsPVmdLX>fl@D3?JbFnQx}qGc9x*wbhnl#{tx*wb$Xju+le@UBgRm0K!+I(y~_FI~CrXDPEc4UoYo7f?J<80%t?(@VnTT^QX}Z)#$(>^jP%iFXk_ktzvGbnzZmMc)N-e1s69 zI$|OuUcpo&D@n$$+KSi@{P{X_8`%_r`XcviWV$cdkPs`&j-693nTxtl{yzpB62J3% z0-~jG!_AcJstFlg7Cwos+-vNM{C%9nj3&XL_u_a2G(pwdMDqI{!0!YRM}dw5R+x1J z4mpW+{Vq$JFnsHFzR*Y5Cg}{?Y{90@|BE_-#k)*qqW)l&jz=ub6(hFc_1cV=7-N>$ zX;gE}M_9@y7z6)4QSl0j*EHh$r7XDGC>pNPQ}LNr2?7bA`lnb2fe10VUZ?}`+0!>EjLXZzhf$K6N?Du9eAq9u^(oM zHq_W1GF?JhyO9F9Q#ns1Ba*fab{~-63Z%Qf^Ih(dNP0cQsO`z0Zj#i7x`t5$Wm~uTnxie=L)QqqOOQ9O&IpL}0ibq5 z$zQWJVZ9Jo-QKbPb1-v&-ZL^~FcTUo^8JX#qBZ~!cK{bJj0^TP#gN>$0nx`hmf+|m zxPY{q^~=6b(vB0+@@=HVqL*#3m%ziV31zXoamNDLZe#m|%^RAPAr!CFk5?NSHx`_7 zmY||EhXtbaL6V4|)+6k0nMIN{!v9+@1Im)Q)@V`UJ@@ZdYkw2LSs}UMk%TYo!5oL7iZ)nnj&+uwi+&Sk}S87AvR%W)|xn&+421PV#>#&h>>QjHH1@LCpX!D z^cbq4n>7^1HHI3W>g_WNb%3r0Ob>Cc$@tx-sv4PF$tG$`pAHPs*s!^$^?vkfwARZJ zL2YGg>LsO%`XyZ$jP7x1Z?}v?#r39-uT5RX-#Qh7+6Bg5$r&6Pq_g$<2{ZEDer*h4 z!*hs-<-)(TP6*X4olfdnB?Aoa4T|5h>}^PiA< z3@m1kyyTflY(#dghwWG3U`{$s9_y%m$Tg~L-xHQ(OhzY^1>8EvxZ?lr zZ3IwV39}w*$}g@lD-x<^YFE0Gf+_pBuzt!xwYOzxKk+A{9_HhL6Uez{u#^ZKbO0)`Trk{uc&H&h&H0x!OiY+HHGqlnTn zM=#zsAWTl{(B#8Jn$PCA(LnPEa+HSzcvXjPG-p{jL&qbub0ul>DZbsFxCk*h$1ead zQ#0FkqlgyI;2TBak-r)VyfsQY)9jGF} z{wVuz1vzboK@I~=$VAjfCA}5&Ozt$Bq3#F1m`l|E-`+S64yfx*7gLWAGsf+2C5wO6 z^mf+QjJL%^$HaJrp3q4D@@qtll5l?Ww=wcX0ntI^$xLkbazzjWPAtDA72CCu9Zm{@ zG~d=@fWwX-jd;Mmi#lJ+X``f0fU{DPeC5ch(vFRE3c;l=6>WsOcNLAybRoie+Z%1cROVsUyPOo`trJM~V zqEE|SWt`jF@>M~})RNUCC^v;r))c&4WpoE|o_gQEWEDu|-~DvP;M)H2`>!r_jIHl@ zr%$(z=Ic{r#khRRowDcF<(Zg`0m*MXR|NW&B&HUy*?jxx`ywyzh1oJeU?slJ9y#SOZR>0qllfzX}gyoaO{FL-W@`=(b4)Q zi#$v&vYbz(X?g$#ki)xg^E2d<1N!aAbfU=K$fV(gFuLs_eM%L9v>~N$@I&>}jN7FR zQ5L_r1YA#`_u%w#VnU+i5Y`e>~$dd&JUdjJf0U#oOh|B+68h~ z5B{->YP0LSSOW$7d~Nr>5bF-XggUY|{F23l_c4b*CaobJ#plJE)imlZ0iC_*y$0ES zuHQT^H65UObo@oU6wg$ucMQ2G5-3qdhEHddMUt!H(`GrQMe~NmB#A}HXyqQ-zaU`F ze&bgukFn+O`yJ+%k6l-GdUcf;gD&T%sPUoAB@BpIhT!lj)zO1U3-gwf&}$m%JXSr& zQ%=DcpUTbM@w2E8go&B5u(ITp3rKKV_EqlFt; zs%3ec7J}|{Y$|tzpi5Y`aJH>QMW;di$h^#3WHRo(E3e))rOR340_E}AD|@ia*NN$d zOP^P4_}V&o+kQHNt=lAE{7;y{o-kZ)fb#a=%Ohd^;^X)`5*(CeE7zhDP-&K$sNyU& z2&-_d%8F|IHF`P2;tdN26PM+1n+V9;1J`PVCkScp`1uPBS4P4cS)L3X69qIYH)>)R zawqNJTEGQyt7+Yhq%Il$eOp5JkiYrxoz%+aBS5#ADHI&K1bI4lh6q5o=<{_Z$bm5< zHf5qOB&E&P_NmBW5s%Bz|1|fD(SIQ=qFzhXqyOvHTY~3QAAV0v8BS0NF*_gM)(SMl z=#VW-XXaBz5qQlE#Sv3N^Jrj2tB745qYw~t`}r3vYJR#2@lZd@Sf36NTa{33gI1FiFI=3P!s7A;O}ozJN^>5}c0_0Rc*t_m5uzj@#8|;xmH$T5_5(1}-CC~y z>I5XC$Vl#`!o5LxN)^86{Xdgh=?kAETYo573F}_7o6aw(}sU0ycxWTX7*g5o%|9Z>$eJkF17tw1tQ@B<6 zTSGi4=anm{VMxp#Z3Mkl4u_9$n&_})t?dLdzs@jFrESP{dCGRIP1f0JWiCwOV7gz- zO9$h({DAY!X5LyAroL6+i0*>3NEZ?f+l6cQzP&+@qPgvB$x!PWn9bmq9qX6yEbc;j za>EoBKX+?DD=LORchLc8ey@k}oldhmA-VFD5?|;q(x)_U4h?N+VejK-n|Sb)rBr;$2J(bC5y=Ho?(m?3!DNw$ITU2IHZX9xzMI-GdN) z$YL!zgh>llDw6wI;?T%f$7gdzyRN#>e!`)xwV5xL^A-rJbSoFn0$?sVKBDI{&9BqD zyX}YGw>Mg2caQZDfkW+7OKsjSrwOb|F=G*zlfq^THqG78f`QPG&}lc@0e!t^5Xk)f zGiL_LD9yg}{N{a1?xIbfq6PGxPfOmAXI*(MtYD@Gm5+6p)|Y?@iPq&Eom{oK02oW> zQJ0mU_gldf=Ojm1Fws*QEjWCTewv|44E@Q^{3310NNqo-yE|dAImJ41={hJvO`jE^ zAUyR4Gp=Z{)C-Z7zok#TxC~{};Z**-y1XOmEdUg`Z!zYyHj0t%pOaaPrYa6L>Xd2T999^UuP;`3-Z{5{BF^b2A@ zV?-g=?YQ&M99ps2oa^K&aP>I&a@n=W5FN+Hud$WjF89uh$A-SJ3I&u{v1uJe*e{zj zHC<<^wx`n&)Q~&u7O}G$w?;)`{u7p$EHZFkpYPWIC>JHb3f91Q?r1C83^Pt_1}z$c zjT>FhEE0t4E0p&k-=n*6*_DQmR?NpjzBc8o1~%k^n6$uj=-?-S8$_s0znJH3wsowU zA8336i#>WSqJ@ykG8vi88-8Fd1p%b5XTD!D3Oqk_WL-sgNNuXNsYvhCWeBjVkWyz# z1ZljCdO|{i;`>;8TZ(9?{muBPpq;s2x@XQdquA^IPMh2U#v$S;&G&IpbW=q!Zh$zE zq;-DK4NatRzH4O|jCS$OFWRs>3=YKjS%Zf#T85OFY|77G*U_;{+c^wCuDR%1sXz z0TezrL<4A#kQ@axPd=QO=0w{ue~qYkgQZwj==`wHoYC?2ih1iLF1dWSmuzjqh?Q$P{eM*@@6lVVT=0$ z#G~b0&mjb1n;h|3)t&ek4j)NU1#o0Hks^K8c8{_@-Gy2Ua~KOIE5fBfQ)fg^>dR7! zR=S*qX}8P=u-;Aoa~nT=v`Kj+n2JX`SUKTM~)~6ix6t z9M666{dHK~oP@>*XrbBI1DzaNfT(4DPN|+Yy|XOFQA|EtU5%rwbV$g7D@RPb;7m>q zu1n4Ap^DmL)>A=YHestHDGV^S9=j%I?!rTg4wDptZ`{=1cpDSO&q;vfpm`@NUqgRC{8u?uEw)E2XU32`)5WTQ`P!hF8!r7>_362QB>7h@J|K~n~yi8jlyf_u4 zM;ZwEhCo~RBZFU}cG$F5?HvKOHFX-Va-2ZY_;VLE{DU61uUn7D*^1` ztULp$kJct8T-br03}(Wg24lbqbCDVrR#E)MS!<8Za}lV=gJYs+oavr1bXk1HmgA2v z6xdFajCFh)%#S1?xkf`u0vdX|nR!xaaL1_r=Nj!7Fo#87<>s|J>I=QL?Y9oA*;d5~ z6!^I5^*L1_e6pSJlXTF@0SrD?S7EcFIapV*1DNH;SY6bX&WRK7l?6;}NJ{~<%N znKdh}u*JKmlXN#5803|@TX#d(0Azo0C(L|BSO;uLR=oD_s4}rrWKE?l+HV2_6yre{$>@J?f*U>o= zOSy-qE?~L}6g;}|oCVemla5Qlg=zLpJHl&2Y<&2@&fEZkbw057crn{ff>VEnUf@mC z%SYb~LL)B!;pZel8Uq@)9?%?cUkb`|?#@IPZT|Y2Yl)GsJg&!s(ht$Qo1c^1P(M6C z^;$jh@>MN&@IprCUZ@*DO0EwtL{fvs=mI~z)D%Z%GeeDXpR%UHyaC|p|JN?nFTQtw zu#Bmu3zG7l<#>kc?OvRt@C$5a#e&)7y6``*__?7V1(lj6nd2@FxUEcYRsq>L$n7pv z)~)GYWU4d~P~xDDvs?=jeWV|#ME|73t%B>VM2tateqKq4j?-JPu%i#p$*3%)ly$DV z+s1lOT8G6jh*aq603t`k`yNOL_%8D0IqAvpN^YRDC-Z)_!ANpnLD%H<&NNtm!cdjk zWb^4k@n$lxdW7fZFZx;U4e}jC^9)GD`78ok`K{q&sH&s#UBGo@B{!W1mbIz9P7XZg zKUY27HxjaRlDA$OGn>r}G!rbsj#6CaKx^)W+MF-~LRdX8d%Ah&rzK}W=SJ25(5A&X zq*ktSaHGwL2bhU^)pMaoJsqy>b%QDAW@wM+l~p+}_=;bddh#(?4~pyOhnrVP_9|8$ zaiI+OW-|Hk5G%6ATfJbAm6GQs!D$)S&~6Rm0k1GX!fNq1Ho3Q6S-ANx0E3_*bs+F= zoVuuu8^Spd+9RQIxv5HmVwC5|R0_Q?$XR&2h9tT<^W(-?x9*ZBYH9&6OsH_^%{|wn z-tfjM%Sr)Ua1G|AoB6zanM-{z4^lT-jV0*4ub}~d_wos|#pA8vBFm9522u`=dk1;J zlk(8&%?2SL{x8nY6d{gc+-}cZJpi+24QMFycfU8t?rcVpD|}@Z7%mUT|Iyt?eVD{1 zGGLk@(NBe3fMWdT@?h3`GL;Xe$A^oT0{qKK?)=QW@>V*;B|w6ffDVI8VinH|q;6P1 zLw-MSNY|$6p8^fvjy*#kA@kg{DTD7q!>7Se&hJ?O>6SSrx;TT2^04uKWj^Czq}k`4 zyHmcqcr<9(;p$7*%g1f!5MDG0%Cq523JRiEgHwEay9{w(EMD)PAWniL`ELjvgf9TVOZ9}OB&PMI~k?9-n;6LIv@`^ zqK4iRos|Jo{FL*Yv_8F_?wKCLM(qu{mB?0A#R`p7p%cKIaI9zmp+cVbFCur);6#XD z%4D4Z=&;EJZmBqg5ZuIi-<=nC4tCAC>Jh}ge>rfK@Q|M~pC-X{M7l0>(f>W8NyH?G zYCJ5@h~*FuYH73tj)>eh2wEIY?~+*4lW0hIJJ9BDAIicXf*QFI{&|&wEJf}4E-;Z8 z&A2ze$j7x5#}(AD$H)_b{TI0u&%G9e>gFYNh0!d&!;~tsTK^L~FWl<{oi{}yT!$%I zdN^Aw^!bp?4}a<9HYdehQDi1V(QR+dk}gUz-2RtFWapkg|8=MBu+jb|T9p!3cy%d~ zJ^OCthvliiDx%f5>r^tCRkf9gJsc#UY+&H?=U_R=yf58jB%Q2-X*bJcN(2?P7AxL*tg1h{7#`}*U5L?8E8D+0eje+$MR6mi|^=i~^g*k#^)cNCM` zM~}A6R}GG>Y?3_0z4&>{?U7-mW3STt?Y0(bX7H=t+<2HzaZW>?G*hm;#c&RB*C|+n z=+$e$;Q#k}suF=8m*HWtGg(kiL$m5c%PS|KfVmk()+gIc$kaa6!1YIdf89X zSHTaHRL&{7hY)xt%0cxqi;4Bq&fFMF(1IOiTBhmZw1607Um}Wv17ZP$=dI2^&jVYE zK3?2gD?k~R_Ch1&DZMrG<>OQ^?aHEgJlAmYq1}7VUnq%y2m*atRi18Ytb@!=|Dw__ zf1fpe6_|LSGQhrx$$+_jT6lix4G>g%XAQQ>=nOOuj^fSGwp3M>Ve z-nj`)k}*nrA2^t)_kS&5Ab8NmC6Wvw?hpSpi;zmQafH-*WeNkJ|LS_3Gd6&TC1(Q0 z_55td4IsjiCwSnu|FdkkQB|gaY5C`(5;&~ZXsli8yeiAl@^Pe^4i^)26P;mn5Rbd! zS%9w@ef59-$B18n#1VIElFq745TRw@ou19>+=%{Qx)iy~4n~=BKIp!N0l5?|tl$#- z=3Dlk-=Eti2olfFOEJi~7eEb0cg`Kv^Ev>in0A|!$zoF4bPDGx&pU+ZNE`#Fl;Ds& z$fpMUvhwLUlPJW5667;;NfF4I(=)-b0%hsWDYmY9i{Jd8wf&mp@7$D8WXj_OX1&KHTDy`}VbhpwgQITe zJN`IU1^DiE7H(VS4!l7dabfO#n z&%(=6>R?+{8ZTXc^5)p>$IX{M#-dv>Q{~ekM6Rx`dU+>1I;VV3Ha@>5W=!~?5AObp z^ddA72hD(W#%kK%!}SbNC3uOgP*m1kY0)a8Lnrif&8a4R3NM z-1E;ZtQD%>j3|_C)Gn^cRrtk`7`1?n9Av7U)gVOY^t8`5@$gXx%15Lgqa z-3Yes6rA%(PEL-U5`yK2=KlK<4bxaidB~jT zUd0r+9d1dY%@{O3$l&DrY+2i{Zq0RFKgA}VsvT3K!LDI>1X0>frjL)-(deJ#I`=}v zPWFo&S4OQ{=%e~O+=sN@`|@->;o08V@hz(X)oUM8`KxPALsgBGllx8U;6W)u<8`Np zb^cxMCtD6%r|bS=#EJVq7#kmkK6%TtY{==XcB@}#r(ZFAt^Hm!OgMQsibTb-`{fu9MfU`|9m14(JW!C{K-axj8!QhfoPOPM)kx`l-|`Di7!x zf46z@cyG)Oje=tub9Igvy9?Sls@Rsts{~v&!xM>#?y$Y``w1C|n>?EJo5~x8?q657@BjLCQbLMEgwr`mlI>J>rgEwyTN}zYp^}}1$u>F| zMWs@dZB&-X*utPOBNLKPWC@wZ*tZ$GVa)vQM<3^VKG*k;-*a8AuF=fndEU=`zwX!T zb-&;5S?sHcp*}n*yOTSD=VtG%n#*<8f32c67i~rQZKtd>u~zg)7da#d)97Y@PT1@C z8%TdFrw+R-kHTGQVGlMYN%XRCnuxdQ*uUP!Wz!wULOVB@9AsoL6l)Z%Hcz6pm8+rLdmvw2P;Wlbz#GLv)fF{#Uu(8sdP+e(JjV$*a0)T$ z_fhk=y@v*;8Kp=g`EJD)UsD}h+s~y= zKlc9a*CF2IyGW1f2&OQ}%bT;7TN23IkqAFek@I=Kjb4Z1`h35E;E^l2IaObGm+vQa zdN+;h|Gjv5enJqn_)Y|BgBttshM*P=l@pi4PscsCd;M^{+{S{f`~ed4#u^a{bD4CK zTfQI~{gIzw&-z;;LPpm=;-u`&J$fTX<~iK=thSp&DOM7>OVbIIUF11}SDSEmiJKLy zh{dC2N*!yWy_uB@?HkAj(+^|62ICLAS&HskUaUjO$_9-WCi-a7f_)cjR71CYe6NvT z8MFX{l@{QbHngbFn8P1b6cK!)h7rgABIo(5IA_yfs%28kgsKh=9@oSgbGX#C*sp+k z7Lgk|`%*3v$t)2pI{~{hl(`V9O5)Dtn@bTxmx?Z^`j=1D+&x#lYA(e5ZFBDEo5A8k@5QkyG%}@vg52X;Wv@Ck z)w@8}44!+9HZ?$F=#liPSZpw6@F0F?6N<<29?3|+QMc&k>UdzW*p^Rz?>0z8#HfZ>Mwews>Wm9hUj_GP1#}cb7Sy{dp%YlKvVRi8X;(i@wIG??(^u42uW;L0t zeeb+TrL#}QJ+}hy;J}o0WxM0_+}Tb9+ia&liXr)QaE;!pF=TG`KcuI}nYqHEY?jW{ zdoi0t|95|Hcy3X*%hzOD3`uMbX9%U5qEU)nbyx#xwl={sj`m1mOM#`-k3_|ybtU8K z7m4nphyQw#(ruYOM7h>Gsy-^m4R{CLHNW!)Ck?~f*e|8Yp$JVp7_~-|ZqmP=Gbc!sp$4*YtSS*Q zBIX1$T3RwQMpQf=A-&92SKdkKqFUOuo>zC2>~*DylQOcDfD`s8Ks)_4*nN5=E5+Y> z8mOy@a^XA0Qt#ufyN4}=H6y?5%|>q0>HH6g1LqS+?4C~KG!R-BwEdP zZQS8+ABkx>a$0C30Udh6^vJawq~+^9;u5tTmsEtToHsd+tR_q{n7x<2L5YHf*{9-> z#nW{bNULZVe*SYDIVZOB{T;$tmp5NA??W&hrVF@MS`fBKb)@-*DAfjB5^Z)*OqiWi zOY~6Iu#{09T=v0)e-Z0_pHBsy&9Bx5)R#I2>(6Q79vkweyLSv#ULU%}x~pIYYtZ6h z7Emra^i<1(9rAUT7CR&BWGAgJD3@9VjRBc=RT_)@~POX5rXi>h_}dGakms|e5N zv2oZ}psasf3~H(I$MsI?>Dz^cEjNm9xQ(dURtuN)?Z@99(;A<8o&?N1M^|%Xe$x$T z2z>fCw-!Dm*!$i;w|FA^<=DOFFQcRKtZi3pOO;pi@{!g32-1gaTvt5K^CE?$TE1o4 zf#Uv3DYaE{Ho6tU{eMHQwc1cbe>-;o&6Q_!X0uTGdPCaH+4sAV`rQBUWVJ(-%#Kio zX6WyqbrEv6*e5}eoP5h9d$$-VM532V(C{UEz9#fyGBu2AeNa8JUE$* zY{=}F{uyKY=|`VCtazgRz_7O(EPrd>+dU0?ZZy!@&Dc;3;Opr!5}rGoMfb%o+pgB^ zdIkKh)tdG4z7B+?ns1Fi!M!uH+r#FJ)Z(2nq_x@Fo_S*lYOgF38=uZ+G+q|3bftWZ zDlHnT1YrbY|4Hv3J}wv@9_}#k@d^FCn7NSeP?NCS%J9iJ`tkx(BtLjSiTd{j!RiI_ zLS=u{q%MYascbaobu}L73-02ZtMbn)mYEb3QZ+X-vAkuf7kALB-Gmp1=j=fc*7%q` zda+vK(bhIV;udcib&?-K(FYn5qL7M<{3W_iY>;i^AwXEcAv`v;NWV@dz;>~nNET0> zy6e134G&yA+kMKr)l@T5lSCJ$Ln#w9zxnZ8HNsoGfiFaf1VhWKCVG;RE(or`V_`49 zgTZ16G96D~0uyZ1v%m4oz&-26u6I`xUpPJ7RVUDSALBVagp}i+Fc}z(%Q_g41#mpI zOkM67+Ut<_HGn*m!kWSJ^mdvtIb#&A+ad$SWw2^B$?Dp20pIQ4=@3a#VYLhNT4S?K26S zqScKkx{{}71ibF;bE8F5^Ld=p=83J-eLBncFJ3r!KqI}eKOWff)9f`#szylmt`?xn zap`i197-%!&v3|#6qMi(9D-+c8N*@7$w58m?wMU!P@wKmw%!hOPXZB53b(R5lf`Rl zUsIM|Q#KE!263m zJM_Gdp^5bUHWi*Bc(n__16^|f>N6B3(hg7WtDDzJ#(_nRNCfw9(idr z-A9o>*nYXI1NWxcKv!VV_U6EodQLXN2x0G&EA)07FJXjYQl^zCqP_E_M2q!DE>4(8 z6A$AO9DgX5{#o17wp)Is1_yxSO40ox6fE(wa(tjt>OS%PEm7)6 zwBs8KG#YK1rovBvv|>imR=(4OX-nI4dBfOW?__&1m6kAVnjipcHd z&Jz`Gr6+k-C_dxG+jwRRo-Wbx5b`2ie6v?TtS0RXA~4DGCHavhZXGyXCh4GiSFy{d z?gtg@!6a&)-P=cM=Tz6*I;Z$qB4qH{cms2#mnq+9#hW$fZRd_Ra}SV4E%G;0W{oN5 zQ|1%h@&ueyZ(bE$a+R+t@f&Mr$w6p3fJ7F~S_Vd$9FcFpV>c_*&5xmZftZ%K)a{`w z9aw$$UXr#RyWTv{Z25&mh%wJwdIDfTL?mHNylNC+Eg?c*oDc=zsI`V%Ep{y;~JQmJN! z@R`DOqw=aZjp*0C-cFS6IU?R&{-TpR`?mz?*I>xKd_nUPorx%e+2^cVBEi23TvA4p zNjjWJ)nJDUm4SX2N?a{4^KbC_nI3=D;0nz{M$Z{t%SmwbwGc9^DfkNawKU4=JTQ;t zHuJKdmF6CO{Q)Umy>od1k(v~CBjVS%>8vD>XERu!iZw_hre2$k6I6s(!I{mN+m`g{ zYIACeNrFg24a-VswuT{LQ|?#=9?RKpJM!$M+$7y|K13B4wZs!33FDZFA0Se)*XCq} zxhU|LI(jQr06MH{>cpvyTXt;%GeaUSo&eEX^Z5&SJUo4Y>jRa2;`y68(Abcc{o+Zv zeN4&II{gm}YxO1&0=_5hFce7dRYZuT>BPR=uspx# zwZQ=P$MNTt$@ySWAjBf-^e27j*p+;q_|?Wl@h-nM3oH2{rU;KEVv`sO$zgPRY-cm( z#I!#Dfa8O)lKc>lmb@omtwU849gTih5gRh@gj5&fYG)t2C>j3?aFZltUEqUP;gfQK zS|sdpK+bN$0cE6{aej?*R29Dg^_A|R$RC0zMfqEKh|SbC6*NCCAY!n1NsHYdH#Vp& zwWE?D!`uX}8NJ0=RhYIi84&lNtpUgZtCU!&ybI`nQ{w!8Df6->q^3Ic;_l4XfeSzf zPvUCXOp`9PHFduDGJO41s-jESRdVZ!rnc1X z(_!pfIf*Pki719)a9L{&BhR|b@$Fe5ZT;yFzau`-D*(sPsm_uMWA_e==^63 zaAVi$e>h1VC_Ui;iH5j4E`HXO(2$nj<`D1CDN%#J091cg1n~Mi?-Y;$IjYyeNduaJ--zy{=HfVCs&;SUaJ9!6{Z> zW1L2zcxqEXc0Y33Ax%6xwF~y+ckhm_JA@ry$M^KvFU5gwz~7Ca7uC~?1DL&^-3sUS<8mE;>>mTa z$#sz~g1yiY-4?B&M;9<+T;RyW9$kw2h>e}FQ6HbfgC%#k;ZEJO0*Y<)hr!&l=HR^u zNNvH+p2Y3}bMeSMz~aYMh`mVo{EuNNw%(@j%E&qV(s`}Hr%u($+PgY}S#0pxWL;U=Lg^ue3*E9nMGDAt@N+rat@T%|!Lr4nTMz09Obut-j zban3P0H~&iFoVz+%CBG1;&EnHN#0(9HFin@`!M1$kcC5;16K)lZ#77~6{ZMcTo#e- z*(o%33JY}WA1*b~D0MZ7<~q>kIY)EPCr^S`8#>LtuRHlyDD8TKG98B0#GtOAqv%RQ zHQdD4ba^FJ*?eP_@;xqJqKDMymO3Z~i1eddhF)o7 zRZEZybS4m<>fPg07YUAHxi{AF(j=G|o?um7{RR!&f>8EOgy+>7{zrd=xxg;zY$LJO zjo=+s%tL~B*7(0d{6G@G)L)sXnNJ6)C&A>|(>aG&6}eEs^k$wl3nAJ&F&VMqB04H3 z{Tz7Q705bpe0(r7Ni^FxJ^q;!Os08PTHlfid9jZLZUqi#myW>o%9#|V5P*P_wWp=+ zRd;&ecwyZ!)f$F00ZlP**&NfyO}^1$L`UMlt*HUO@(mxEq8rfu3CkWxutMQO=D!(* zy|y!sKOjK-?r(!>P3nF8%Y2`D485?`9HlQXR^x4NDfGT3&FZ0KGScdOdJ!60##E#W z#CNSNmyQodcnqJKC6+sc?QOY%=deHU%E|w`&r>}U_Ccj&j5L?^#ngK(?B;8|Ly1zG zYFTF0L^0i6mQah~s7>&B_4nKTmUuJ-^B<|5cQ11t1O#{KeB*51^5I6OLzJI;m*igK zEPX(CozfmKYa^B?->M3fm_7zW42P(OWK7>3`m2}sH;b*B*Y^#D{0{40<>TAB>J)w! znBvjanvqp^1F8iSiuF9)y#t$kQ(LE8V)dp!q^IPxHryjviS6u-9&AcU;+UjM?h1lB z3E7<{j;vwfyYXm^&%g^fjwJkBR7b83i_X8>Sn!@S<28>4UR*+^$y}oWGy3;#9LTBo zD%R>B@&boUVuydzTL1N7jjNLwy@mF-NsVU%lL%74qZO#%gEO z@;Ahp#Gyg8Zb7VgDq_&#{;%Tf(99d~caugg5U9?Y9uIS2{CF4c(3)vl-;Ai+`}*^p ztnbWF%~j+;5*xvQ;SBJUSkFa5_KkmgqX4|0jiQ?P{{Q8f_~BO1?o#DsESFuctv6Z5 zs3tXieY=wI#M4l>VWM5*=cTPhy_$#K*v00}h*e&#ztD5;pdPN= zeyJq^2T5reMX({NxPPxW>@r;O<8Q%gBZZXh_CVsIl=4Y`Ag=4-nL5i%-zIGZb~z+7 zmC55W6!tAaQdK!?O;{akwsPB03|}qTu=R5rfSHD z@$-j>uF_j5Zx%+lB^6Wi!@8%b^U@PP&~lM$5~$6gaAiO2D*f!B0-NkbBd32Kg%rD4 zHdQN4?J4(li{BR8s7qrv^^@MuoX$-7Ck^=468EWw{X#{D{JQ6oAC{KEdQaC@oD0A% z1k{_4f*}wr`dA0bf+0vaOm}zh4l^x!MEWg7{x^=s=UtIzA7a%)=gVN5J#VQa%I>Yd z{JYnP$XOLn*A6I|QLcS?u}5h8&>e@g-@tpJmv(y!O4wr@7JG&a5^@)+Zmp-2W|{<( zG`;ek&4ZwJz5_On>fc0WjOu4F9KvX8P;?=;wLTo@KkW_aZ^L2*y#WyW7jH zR_^;^mUmE17;}icAwh`bNs~jC=T{S2doo+nB<6V@#>^i4nY|9#j98Sxw>qe#Xko;F zY5c>HH3^0X>XDkw@Ba$%wf=XQi{t$GH>1DC^0}k=)mDMdID}Wtgncbl~+EPco~zdf-7+}Y(=_m zqknMgc#CrNOvHVn8_F%6rh3RqNu8=b23<%yAn)~vzmIfYB2Y^&Q8{J-5bg59j(ASL z0%BG@a`2I31jhW2mAd2(L)%sNfmx_Hia_Ne(xOR)&WE^XLz@*^21K)Y+pgF`ke}xo z7bG7nB|%H!vP|I)Y3D7eAU@bs*XMn9ni0+b>kMAN}+A@$bX60^81L_8rqac6|HxZQ^IPDtK3En`vS7 zH;RhJEgm29yG@a^ozzW+Lep#f;kTQU=PWHPJy0d*GWZJ*Iyb#fK!-ky&48u6D@zuz z9rcSBFP^G9odD*OS(`j!&@u&d?|nXnn}~1KKkV6~HG0--XNy0ug}%#T=<2}r!9KCz|y*Ydw-?oNUxIA+Z_ikJh1uLvTN=P?WSP) zPe}hU(Z25|T&1nk@*lsj#d89>giKAh_5)E5HN1M0W1l1>Se4pZERJO1T~o&Ddr68;Z+%~N-Q z_juQ(HFJR7;s$zir&XTqodkMsWsrLCB5Sxc%iL2~TIa^KUC_qIleUrL(5A*D%~(jefzm7* zmRH$TZb`5n@HTU0B&sye&Cc+ml9?=_Y#R?;z`haxkHjGzvBtlyf0;%$*a758QrUEUe=Ck+D4V zp@HphmF5|`WnC51kS`23ys|)xWm0$s8t$E2p^befp!a6hw5@Bwsdbr!PIZ<+jWBME z{`xq$ycpNIvp*CMpQuzh8#dgG;@yRMQoA_Ccv)6fHf_5(Bjdpt3kZ%oMMXuO<-X3I zV#|X`UegQ~omvRPsqo=?Aj;n(wX#VpH5)qV}}$}xfxD(|!Kp;8u1 z#?ssvJNS_SLsmcVtG0O{J_ko1)%Dul@$){`itv$ebx`2LaO0f04)m9|0nA}Q|6Mva z_O<7uzwY@0pRG7mOY z-&j7SiGVh*2%K|8;=)T#*Z%;D3H+xiMK^)?8iz}*8UFHeCXruA@%{s1rY`oy zs7?o2CvD%i%pSte%%>df{P<&J2?7(?XY(v8hIzEi)|h6_mj% z$uxQP$cB=`zwqBE z^^m36r@8^2TdvA0zACv~|7daiO>@pYCX5OPVnF-rd3$@=ds2r#-m_4$%F7jUPSNq( zUlG8xXD5CQdvd_KcpuD3v2+qvBw6?c|Ka-e)yD~-X8mJ&ueVp9y7fZcsrJtV6}#&; zmKFG~uMT_vsB(2mI^^<*Y6sxaG$d;fENPmganCG8r{=@M-^WO`KuD%Z3rt$9#O}!2 zvywR)inf;9et_<_a0e1e!H;lT0?+LuKrR*nK4XCO_TAKLAVpe3&kBwsUnsG4M9=S= z`7ri#Dfz=m5Bd=!OKB_ z80?JNsV^^2o}`gOevV;SN9@`;y)vE~M%5-|0+_LXUVl~6nu`8;V!pH^YXs^)zu5e5 reFVQtI}*(e(F0HMUx$4eZ=GuHRjJgccHpJ*@fjUA`!ny@rN93l%7xjk literal 0 HcmV?d00001 diff --git a/shader/instancing_fragment.glsl b/shader/instancing_fragment.glsl index 493b423..ce97a8c 100644 --- a/shader/instancing_fragment.glsl +++ b/shader/instancing_fragment.glsl @@ -1,8 +1,9 @@ #version 330 -uniform vec4 colDiffuse; +in vec4 fragColor; out vec4 finalColor; void main() { - finalColor = colDiffuse; -} + // Advanced coloring. CG lecture really paying off now + finalColor = fragColor; +} \ No newline at end of file diff --git a/shader/instancing_vertex.glsl b/shader/instancing_vertex.glsl index 3af83a5..b9573a4 100644 --- a/shader/instancing_vertex.glsl +++ b/shader/instancing_vertex.glsl @@ -1,10 +1,14 @@ #version 330 -in vec3 vertexPosition; -in mat4 instanceTransform; +layout(location=0) in vec3 vertexPosition; +layout(location=1) in mat4 instanceTransform; +layout(location=5) in vec4 instanceColor; uniform mat4 mvp; +out vec4 fragColor; + void main() { + fragColor = instanceColor; gl_Position = mvp * instanceTransform * vec4(vertexPosition, 1.0); -} +} \ No newline at end of file diff --git a/src/backward.cpp b/src/backward.cpp index 7eb1740..659dd6f 100644 --- a/src/backward.cpp +++ b/src/backward.cpp @@ -2,14 +2,14 @@ // // On GNU/Linux, you have few choices to get the most out of your stack trace. // -// By default you get: -// - object filename -// - function name +// By default, you get: +// - object filename +// - function name // // In order to add: -// - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) +// - source filename +// - line and column numbers +// - source code snippet (assuming the file is accessible) // Install one of the following libraries then uncomment one of the macro (or // better, add the detection of the lib and the macro definition in your build @@ -39,10 +39,9 @@ #include "backward.hpp" -namespace backward { - -backward::SignalHandling sh; - +namespace backward +{ +SignalHandling sh; } // namespace backward -#endif +#endif \ No newline at end of file diff --git a/src/camera.cpp b/src/camera.cpp index 632ea4f..41d2dfc 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -5,69 +5,66 @@ #include #ifdef TRACY -#include "tracy.hpp" #include #endif -auto OrbitCamera3D::Rotate(Vector2 last_mouse, Vector2 mouse) -> void { - Vector2 dx = Vector2Subtract(mouse, last_mouse); +auto orbit_camera::rotate(const Vector2 last_mouse, const Vector2 mouse) -> void +{ + const auto [dx, dy] = Vector2Subtract(mouse, last_mouse); - angle_x -= dx.x * ROT_SPEED / 200.0; - angle_y += dx.y * ROT_SPEED / 200.0; + angle_x -= dx * ROT_SPEED / 200.0f; + angle_y += dy * ROT_SPEED / 200.0f; angle_y = Clamp(angle_y, -1.5, 1.5); // Prevent flipping } -auto OrbitCamera3D::Pan(Vector2 last_mouse, Vector2 mouse) -> void { - Vector2 dx = Vector2Subtract(mouse, last_mouse); +auto orbit_camera::pan(const Vector2 last_mouse, const Vector2 mouse) -> void +{ + const auto [dx, dy] = Vector2Subtract(mouse, last_mouse); float speed; if (IsKeyDown(KEY_LEFT_SHIFT)) { - speed = distance * PAN_SPEED / 1000.0 * PAN_MULTIPLIER; + speed = distance * PAN_SPEED / 1000.0f * PAN_MULTIPLIER; } else { - speed = distance * PAN_SPEED / 1000.0; + speed = distance * PAN_SPEED / 1000.0f; } // The panning needs to happen in camera coordinates, otherwise rotating the // camera breaks it - Vector3 forward = - Vector3Normalize(Vector3Subtract(camera.target, camera.position)); - Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up)); - Vector3 up = Vector3Normalize(Vector3CrossProduct(right, forward)); + const Vector3 forward = Vector3Normalize(Vector3Subtract(camera.target, camera.position)); + const Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up)); + const Vector3 up = Vector3Normalize(Vector3CrossProduct(right, forward)); - Vector3 offset = Vector3Add(Vector3Scale(right, -dx.x * speed), - Vector3Scale(up, dx.y * speed)); + const Vector3 offset = Vector3Add(Vector3Scale(right, -dx * speed), Vector3Scale(up, dy * speed)); target = Vector3Add(target, offset); } -auto OrbitCamera3D::Update(const Vector3 ¤t_target, - const Vector3 &mass_center, bool lock, - bool mass_center_lock) -> void { +auto orbit_camera::update(const Vector3& current_target, const Vector3& mass_center, const bool lock, + const bool mass_center_lock) -> void +{ if (lock) { if (mass_center_lock) { - target = Vector3MoveTowards( - target, mass_center, - CAMERA_SMOOTH_SPEED * GetFrameTime() * - Vector3Length(Vector3Subtract(target, mass_center))); + target = Vector3MoveTowards(target, mass_center, + CAMERA_SMOOTH_SPEED * GetFrameTime() * Vector3Length( + Vector3Subtract(target, mass_center))); } else { - target = Vector3MoveTowards( - target, current_target, - CAMERA_SMOOTH_SPEED * GetFrameTime() * - Vector3Length(Vector3Subtract(target, current_target))); + target = Vector3MoveTowards(target, current_target, + CAMERA_SMOOTH_SPEED * GetFrameTime() * Vector3Length( + Vector3Subtract(target, current_target))); } } distance = Clamp(distance, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE); - int actual_distance = distance; + float actual_distance = distance; if (projection == CAMERA_ORTHOGRAPHIC) { actual_distance = MAX_CAMERA_DISTANCE; } // Spherical coordinates - float x = cos(angle_y) * sin(angle_x) * actual_distance; - float y = sin(angle_y) * actual_distance; - float z = cos(angle_y) * cos(angle_x) * actual_distance; + const float x = cos(angle_y) * sin(angle_x) * actual_distance; + const float y = sin(angle_y) * actual_distance; + const float z = cos(angle_y) * cos(angle_x) * actual_distance; fov = Clamp(fov, MIN_FOV, MAX_FOV); @@ -75,4 +72,4 @@ auto OrbitCamera3D::Update(const Vector3 ¤t_target, camera.target = target; camera.fovy = fov; camera.projection = projection; -} +} \ No newline at end of file diff --git a/src/distance.cpp b/src/distance.cpp index 82bd7fe..5782847 100644 --- a/src/distance.cpp +++ b/src/distance.cpp @@ -1,53 +1,55 @@ #include "distance.hpp" -#include "config.hpp" -#include #include #ifdef TRACY -#include "tracy.hpp" #include #endif -auto DistanceResult::Clear() -> void { +auto graph_distances::clear() -> void +{ distances.clear(); parents.clear(); nearest_targets.clear(); } -auto DistanceResult::Empty() -> bool { +auto graph_distances::empty() const -> bool +{ return distances.empty() || parents.empty() || nearest_targets.empty(); } -auto CalculateDistances( - std::size_t node_count, - const std::vector> &edges, - const std::vector &targets) -> DistanceResult { - +auto graph_distances::calculate_distances(const size_t node_count, const std::vector>& edges, + const std::vector& targets) -> void +{ // Build a list of adjacent nodes to speed up BFS - std::vector> adjacency(node_count); - for (const auto &[from, to] : edges) { + std::vector> adjacency(node_count); + for (const auto& [from, to] : edges) + { adjacency[from].push_back(to); adjacency[to].push_back(from); } - std::vector distances(node_count, -1); - std::vector parents(node_count, -1); - std::vector nearest_targets(node_count, -1); + distances = std::vector(node_count, -1); + parents = std::vector(node_count, -1); + nearest_targets = std::vector(node_count, -1); - std::queue queue; - for (std::size_t target : targets) { + std::queue queue; + for (size_t target : targets) + { distances[target] = 0; nearest_targets[target] = target; queue.push(target); } - while (!queue.empty()) { - std::size_t current = queue.front(); + while (!queue.empty()) + { + const size_t current = queue.front(); queue.pop(); - for (std::size_t neighbor : adjacency[current]) { - if (distances[neighbor] == -1) { + for (size_t neighbor : adjacency[current]) + { + if (distances[neighbor] == -1) + { // If distance is -1 we haven't visited the node yet distances[neighbor] = distances[current] + 1; parents[neighbor] = current; @@ -57,22 +59,21 @@ auto CalculateDistances( } } } - - return {distances, parents, nearest_targets}; } -auto GetPath(const DistanceResult &result, std::size_t source) - -> std::vector { - if (result.distances[source] == -1) { +auto graph_distances::get_shortest_path(const size_t source) const -> std::vector +{ + if (empty() || distances[source] == -1) + { // Unreachable return {}; } - std::vector path; - for (std::size_t n = source; n != static_cast(-1); - n = result.parents[n]) { + std::vector path; + for (size_t n = source; n != static_cast(-1); n = parents[n]) + { path.push_back(n); } return path; -} +} \ No newline at end of file diff --git a/src/gui.cpp b/src/gui.cpp deleted file mode 100644 index b07fbed..0000000 --- a/src/gui.cpp +++ /dev/null @@ -1,804 +0,0 @@ -#include "gui.hpp" -#include "config.hpp" - -#include - -#define RAYGUI_IMPLEMENTATION -#include - -#ifdef TRACY -#include "tracy.hpp" -#include -#endif - -auto Grid::UpdateBounds(int _x, int _y, int _width, int _height, int _columns, - int _rows) -> void { - x = _x; - y = _y; - width = _width; - height = _height; - columns = _columns; - rows = _rows; -} - -auto Grid::UpdateBounds(int _x, int _y, int _width, int _height) -> void { - x = _x; - y = _y; - width = _width; - height = _height; -} - -auto Grid::UpdateBounds(int _x, int _y) -> void { - x = _x; - y = _y; -} - -auto Grid::Bounds() const -> Rectangle { - Rectangle bounds = Bounds(0, 0, columns, rows); - bounds.x -= padding; - bounds.y -= padding; - bounds.width += 2 * padding; - bounds.height += 2 * padding; - return bounds; -} - -auto Grid::Bounds(int _x, int _y, int _width, int _height) const -> Rectangle { - if (_x < 0 || _x + _width > columns || _y < 0 || _y + _height > rows) { - std::cout << std::format("Grid bounds are outside range.") << std::endl; - exit(1); - } - - int cell_width = (width - padding) / columns; - int cell_height = (height - padding) / rows; - - return Rectangle( - x + _x * cell_width + padding, y + _y * cell_height + padding, - _width * cell_width - padding, _height * cell_height - padding); -} - -auto Grid::SquareBounds() const -> Rectangle { - Rectangle bounds = SquareBounds(0, 0, columns, rows); - bounds.x -= padding; - bounds.y -= padding; - bounds.width += 2 * padding; - bounds.height += 2 * padding; - return bounds; -} - -auto Grid::SquareBounds(int _x, int _y, int _width, int _height) const - -> Rectangle { - // Assumes each cell is square, so either width or height are not completely - // filled - - if (_x < 0 || _x + _width > columns || _y < 0 || _y + _height > rows) { - std::cout << std::format("Grid bounds are outside range.") << std::endl; - exit(1); - } - - int available_width = width - padding * (columns + 1); - int available_height = height - padding * (rows + 1); - int cell_size = std::min(available_width / columns, available_height / rows); - - int grid_width = cell_size * columns + padding * (columns + 1); - int grid_height = cell_size * rows + padding * (rows + 1); - int x_offset = (width - grid_width) / 2; - int y_offset = (height - grid_height) / 2; - - return Rectangle(x_offset + _x * (cell_size + padding) + padding, - y_offset + _y * (cell_size + padding) + padding, - _width * cell_size + padding * (_width - 1), - _height * cell_size + padding * (_height - 1)); -} - -auto Gui::Init() const -> void { - Font font = LoadFontEx(FONT, FONT_SIZE, 0, 0); - SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); - GuiSetFont(font); - - DefaultStyle style = GetDefaultStyle(); - style.text_size = FONT_SIZE; - ApplyColor(style, GRAY); - - SetDefaultStyle(style); -} - -auto Gui::ApplyColor(Style &style, Color color) const -> void { - style.base_color_normal = ColorToInt(Fade(color, 0.8)); - style.base_color_focused = ColorToInt(Fade(color, 0.3)); - style.base_color_pressed = ColorToInt(Fade(color, 0.8)); - style.base_color_disabled = ColorToInt(Fade(color, 0.5)); - - style.border_color_normal = ColorToInt(Fade(color, 1.0)); - style.border_color_focused = ColorToInt(Fade(color, 0.7)); - style.border_color_pressed = ColorToInt(Fade(color, 1.0)); - style.border_color_disabled = ColorToInt(Fade(GRAY, 0.5)); - - style.text_color_normal = ColorToInt(Fade(BLACK, 1.0)); - style.text_color_focused = ColorToInt(Fade(BLACK, 1.0)); - style.text_color_pressed = ColorToInt(Fade(BLACK, 1.0)); - style.text_color_disabled = ColorToInt(Fade(BLACK, 0.5)); -} - -auto Gui::ApplyBlockColor(Style &style, Color color) const -> void { - style.base_color_normal = ColorToInt(Fade(color, 0.5)); - style.base_color_focused = ColorToInt(Fade(color, 0.3)); - style.base_color_pressed = ColorToInt(Fade(color, 0.8)); - style.base_color_disabled = ColorToInt(Fade(color, 0.5)); - - style.border_color_normal = ColorToInt(Fade(color, 1.0)); - style.border_color_focused = ColorToInt(Fade(color, 0.7)); - style.border_color_pressed = ColorToInt(Fade(color, 1.0)); - style.border_color_disabled = ColorToInt(Fade(GRAY, 0.5)); -} - -auto Gui::ApplyTextColor(Style &style, Color color) const -> void { - style.text_color_normal = ColorToInt(Fade(color, 1.0)); - style.text_color_focused = ColorToInt(Fade(color, 1.0)); - style.text_color_pressed = ColorToInt(Fade(color, 1.0)); - style.text_color_disabled = ColorToInt(Fade(BLACK, 0.5)); -} - -auto Gui::GetDefaultStyle() const -> DefaultStyle { - // Could've iterated over the values but then it wouldn't be as nice to - // access... - return {{GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL), - GuiGetStyle(DEFAULT, BASE_COLOR_NORMAL), - GuiGetStyle(DEFAULT, TEXT_COLOR_NORMAL), - - GuiGetStyle(DEFAULT, BORDER_COLOR_FOCUSED), - GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED), - GuiGetStyle(DEFAULT, TEXT_COLOR_FOCUSED), - - GuiGetStyle(DEFAULT, BORDER_COLOR_PRESSED), - GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED), - GuiGetStyle(DEFAULT, TEXT_COLOR_PRESSED), - - GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED), - GuiGetStyle(DEFAULT, BASE_COLOR_DISABLED), - GuiGetStyle(DEFAULT, TEXT_COLOR_DISABLED)}, - - GuiGetStyle(DEFAULT, BACKGROUND_COLOR), - GuiGetStyle(DEFAULT, LINE_COLOR), - - GuiGetStyle(DEFAULT, TEXT_SIZE), - GuiGetStyle(DEFAULT, TEXT_SPACING), - GuiGetStyle(DEFAULT, TEXT_LINE_SPACING), - GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL), - GuiGetStyle(DEFAULT, TEXT_WRAP_MODE)}; -} - -auto Gui::SetDefaultStyle(const DefaultStyle &style) const -> void { - GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, style.border_color_normal); - GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, style.base_color_normal); - GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, style.text_color_normal); - - GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, style.border_color_focused); - GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, style.base_color_focused); - GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, style.text_color_focused); - - GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, style.border_color_pressed); - GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, style.base_color_pressed); - GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, style.text_color_pressed); - - GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, style.border_color_disabled); - GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, style.base_color_disabled); - GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, style.text_color_disabled); - - GuiSetStyle(DEFAULT, BACKGROUND_COLOR, style.background_color); - GuiSetStyle(DEFAULT, LINE_COLOR, style.line_color); - - GuiSetStyle(DEFAULT, TEXT_SIZE, style.text_size); - GuiSetStyle(DEFAULT, TEXT_SPACING, style.text_spacing); - GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, style.text_line_spacing); - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, style.text_alignment_vertical); - GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, style.text_wrap_mode); -} - -auto Gui::GetComponentStyle(int component) const -> ComponentStyle { - return {{GuiGetStyle(component, BORDER_COLOR_NORMAL), - GuiGetStyle(component, BASE_COLOR_NORMAL), - GuiGetStyle(component, TEXT_COLOR_NORMAL), - - GuiGetStyle(component, BORDER_COLOR_FOCUSED), - GuiGetStyle(component, BASE_COLOR_FOCUSED), - GuiGetStyle(component, TEXT_COLOR_FOCUSED), - - GuiGetStyle(component, BORDER_COLOR_PRESSED), - GuiGetStyle(component, BASE_COLOR_PRESSED), - GuiGetStyle(component, TEXT_COLOR_PRESSED), - - GuiGetStyle(component, BORDER_COLOR_DISABLED), - GuiGetStyle(component, BASE_COLOR_DISABLED), - GuiGetStyle(component, TEXT_COLOR_DISABLED)}, - - GuiGetStyle(component, BORDER_WIDTH), - GuiGetStyle(component, TEXT_PADDING), - GuiGetStyle(component, TEXT_ALIGNMENT)}; -} - -auto Gui::SetComponentStyle(int component, const ComponentStyle &style) const - -> void { - GuiSetStyle(component, BORDER_COLOR_NORMAL, style.border_color_normal); - GuiSetStyle(component, BASE_COLOR_NORMAL, style.base_color_normal); - GuiSetStyle(component, TEXT_COLOR_NORMAL, style.text_color_normal); - - GuiSetStyle(component, BORDER_COLOR_FOCUSED, style.border_color_focused); - GuiSetStyle(component, BASE_COLOR_FOCUSED, style.base_color_focused); - GuiSetStyle(component, TEXT_COLOR_FOCUSED, style.text_color_focused); - - GuiSetStyle(component, BORDER_COLOR_PRESSED, style.border_color_pressed); - GuiSetStyle(component, BASE_COLOR_PRESSED, style.base_color_pressed); - GuiSetStyle(component, TEXT_COLOR_PRESSED, style.text_color_pressed); - - GuiSetStyle(component, BORDER_COLOR_DISABLED, style.border_color_disabled); - GuiSetStyle(component, BASE_COLOR_DISABLED, style.base_color_disabled); - GuiSetStyle(component, TEXT_COLOR_DISABLED, style.text_color_disabled); - - GuiSetStyle(component, BORDER_WIDTH, style.border_width); - GuiSetStyle(component, TEXT_PADDING, style.text_padding); - GuiSetStyle(component, TEXT_ALIGNMENT, style.text_alignment); -} - -auto Gui::DrawButton(Rectangle bounds, const std::string &label, Color color, - bool enabled, int font_size) const -> int { - // Save original styling - const DefaultStyle original_default = GetDefaultStyle(); - const ComponentStyle original_button = GetComponentStyle(BUTTON); - - // Change styling - DefaultStyle style_default = original_default; - ComponentStyle style_button = original_button; - style_default.text_size = font_size; - ApplyColor(style_button, color); - SetDefaultStyle(style_default); - SetComponentStyle(BUTTON, style_button); - - const int _state = GuiGetState(); - if (!enabled || WindowOpen()) { - GuiSetState(STATE_DISABLED); - } - int pressed = GuiButton(bounds, label.data()); - if (!enabled || WindowOpen()) { - GuiSetState(_state); - } - - // Restore original styling - SetDefaultStyle(original_default); - SetComponentStyle(BUTTON, original_button); - - return pressed; -} - -auto Gui::DrawMenuButton(int x, int y, int width, int height, - const std::string &label, Color color, bool enabled, - int font_size) const -> int { - Rectangle bounds = menu_grid.Bounds(x, y, width, height); - return DrawButton(bounds, label, color, enabled, font_size); -} - -auto Gui::DrawToggleSlider(Rectangle bounds, const std::string &off_label, - const std::string &on_label, int *active, - Color color, bool enabled, int font_size) const - -> int { - // Save original styling - const DefaultStyle original_default = GetDefaultStyle(); - const ComponentStyle original_slider = GetComponentStyle(SLIDER); - const ComponentStyle original_toggle = GetComponentStyle(TOGGLE); - - // Change styling - DefaultStyle style_default = original_default; - ComponentStyle style_slider = original_slider; - ComponentStyle style_toggle = original_toggle; - style_default.text_size = font_size; - ApplyColor(style_slider, color); - ApplyColor(style_toggle, color); - SetDefaultStyle(style_default); - SetComponentStyle(SLIDER, style_slider); - SetComponentStyle(TOGGLE, style_toggle); - - const int _state = GuiGetState(); - if (!enabled || WindowOpen()) { - GuiSetState(STATE_DISABLED); - } - int pressed = GuiToggleSlider( - bounds, std::format("{};{}", off_label, on_label).data(), active); - if (!enabled || WindowOpen()) { - GuiSetState(_state); - } - - // Restore original styling - SetDefaultStyle(original_default); - SetComponentStyle(SLIDER, original_slider); - SetComponentStyle(TOGGLE, original_toggle); - - return pressed; -} - -auto Gui::DrawMenuToggleSlider(int x, int y, int width, int height, - const std::string &off_label, - const std::string &on_label, int *active, - Color color, bool enabled, int font_size) const - -> int { - Rectangle bounds = menu_grid.Bounds(x, y, width, height); - return DrawToggleSlider(bounds, off_label, on_label, active, color, enabled, - font_size); -}; - -auto Gui::DrawSpinner(Rectangle bounds, const std::string &label, int *value, - int min, int max, Color color, bool enabled, - int font_size) const -> int { - // Save original styling - const DefaultStyle original_default = GetDefaultStyle(); - const ComponentStyle original_valuebox = GetComponentStyle(VALUEBOX); - const ComponentStyle original_button = GetComponentStyle(BUTTON); - - // Change styling - DefaultStyle style_default = original_default; - ComponentStyle style_valuebox = original_valuebox; - ComponentStyle style_button = original_button; - style_default.text_size = font_size; - ApplyColor(style_valuebox, color); - ApplyColor(style_button, color); - SetDefaultStyle(style_default); - SetComponentStyle(VALUEBOX, style_valuebox); - SetComponentStyle(BUTTON, style_button); - - const int _state = GuiGetState(); - if (!enabled || WindowOpen()) { - GuiSetState(STATE_DISABLED); - } - int pressed = GuiSpinner(bounds, "", label.data(), value, min, max, false); - if (!enabled || WindowOpen()) { - GuiSetState(_state); - } - - // Restore original styling - SetDefaultStyle(original_default); - SetComponentStyle(VALUEBOX, original_valuebox); - SetComponentStyle(BUTTON, style_button); - - return pressed; -} - -auto Gui::DrawMenuSpinner(int x, int y, int width, int height, - const std::string &label, int *value, int min, - int max, Color color, bool enabled, - int font_size) const -> int { - Rectangle bounds = menu_grid.Bounds(x, y, width, height); - return DrawSpinner(bounds, label, value, min, max, color, enabled, font_size); -} - -auto Gui::DrawLabel(Rectangle bounds, const std::string &text, Color color, - bool enabled, int font_size) const -> int { - // Save original styling - const DefaultStyle original_default = GetDefaultStyle(); - const ComponentStyle original_label = GetComponentStyle(LABEL); - - // Change styling - DefaultStyle style_default = original_default; - ComponentStyle style_label = original_label; - style_default.text_size = font_size; - ApplyTextColor(style_label, color); - SetDefaultStyle(style_default); - SetComponentStyle(LABEL, style_label); - - const int _state = GuiGetState(); - if (!enabled || WindowOpen()) { - GuiSetState(STATE_DISABLED); - } - int pressed = GuiLabel(bounds, text.data()); - if (!enabled || WindowOpen()) { - GuiSetState(_state); - } - - // Restore original styling - SetDefaultStyle(original_default); - SetComponentStyle(LABEL, original_label); - - return pressed; -} - -auto Gui::DrawBoardBlock(int x, int y, int width, int height, Color color, - bool enabled) const -> bool { - ComponentStyle style = GetComponentStyle(BUTTON); - ApplyBlockColor(style, color); - - Rectangle bounds = board_grid.SquareBounds(x, y, width, height); - - bool focused = - CheckCollisionPointRec(input.mouse - Vector2(0, MENU_HEIGHT), bounds); - bool pressed = - Block(x, y, width, height, false).Covers(input.sel_x, input.sel_y); - - // Background to make faded colors work - DrawRectangleRec(bounds, RAYWHITE); - - Color base = GetColor(style.base_color_normal); - Color border = GetColor(style.base_color_normal); - if (pressed) { - base = GetColor(style.base_color_pressed); - border = GetColor(style.base_color_pressed); - } - if (focused) { - base = GetColor(style.base_color_focused); - border = GetColor(style.base_color_focused); - } - if (focused && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { - base = GetColor(style.base_color_pressed); - border = GetColor(style.base_color_pressed); - } - if (!enabled) { - base = BOARD_COLOR_RESTRICTED; - } - DrawRectangleRec(bounds, base); - if (enabled) { - DrawRectangleLinesEx(bounds, 2.0, border); - } - - return focused && enabled; -} - -auto Gui::WindowOpen() const -> bool { return save_window || help_window; } - -auto Gui::DrawMenuHeader(Color color) const -> void { - int preset = state.current_preset; - DrawMenuSpinner(0, 0, 1, 1, "Preset: ", &preset, -1, state.presets.size(), - color, !input.editing); - if (preset > state.current_preset) { - input.NextPreset(); - } else if (preset < state.current_preset) { - input.PreviousPreset(); - } - - DrawMenuButton(1, 0, 1, 1, - std::format("Puzzle: \"{}\"", - state.comments.at(state.current_preset).substr(2)), - color); - - int editing = input.editing; - DrawMenuToggleSlider(2, 0, 1, 1, "Puzzle Mode (Tab)", "Edit Mode (Tab)", - &editing, color); - if (editing != input.editing) { - input.ToggleEditing(); - } -} - -auto Gui::DrawGraphInfo(Color color) const -> void { - DrawMenuButton(0, 1, 1, 1, - std::format("Found {} States ({} Winning)", - state.states.size(), state.winning_states.size()), - color); - - DrawMenuButton(1, 1, 1, 1, - std::format("Found {} Transitions", state.springs.size()), - color); - - DrawMenuButton(2, 1, 1, 1, - std::format("{} Moves to Nearest Solution", - state.winning_path.size() > 0 - ? state.winning_path.size() - 1 - : 0), - color); -} - -auto Gui::DrawGraphControls(Color color) const -> void { - if (DrawMenuButton(0, 2, 1, 1, "Populate Graph (G)", color)) { - input.FillGraph(); - } - - // int mark_path = input.mark_path; - // DrawMenuToggleSlider(2, 2, 1, 1, "Path Hidden (U)", "Path Shown (U)", - // &mark_path, color); - // if (mark_path != input.mark_path) { - // input.ToggleMarkPath(); - // } - - if (DrawMenuButton(1, 2, 1, 1, "Clear Graph (C)", color)) { - input.ClearGraph(); - } - - int mark_solutions = input.mark_solutions; - DrawMenuToggleSlider(2, 2, 1, 1, "Solution Hidden (I)", "Solution Shown (I)", - &mark_solutions, color); - if (mark_solutions != input.mark_solutions) { - input.ToggleMarkSolutions(); - } - input.mark_path = input.mark_solutions; -} - -auto Gui::DrawCameraControls(Color color) const -> void { - int lock_camera = input.camera_lock; - DrawMenuToggleSlider(0, 3, 1, 1, "Free Camera (L)", "Locked Camera (L)", - &lock_camera, color); - if (lock_camera != input.camera_lock) { - input.ToggleCameraLock(); - } - - int lock_camera_mass_center = input.camera_mass_center_lock; - DrawMenuToggleSlider(1, 3, 1, 1, "Current Block (Y)", "Graph Center (Y)", - &lock_camera_mass_center, color, input.camera_lock); - if (lock_camera_mass_center != input.camera_mass_center_lock) { - input.ToggleCameraMassCenterLock(); - } - - int projection = camera.projection == CAMERA_ORTHOGRAPHIC; - DrawMenuToggleSlider(2, 3, 1, 1, "Perspective (Alt)", "Orthographic (Alt)", - &projection, color); - if (projection != (camera.projection == CAMERA_ORTHOGRAPHIC)) { - input.ToggleCameraProjection(); - } -} - -auto Gui::DrawPuzzleControls(Color color) const -> void { - auto nth = [&](int n) { - if (n == 11 || n == 12 || n == 13) - return "th"; - if (n % 10 == 1) - return "st"; - if (n % 10 == 2) - return "nd"; - if (n % 10 == 3) - return "rd"; - return "th"; - }; - int visits = state.visited_states.at(state.current_state); - DrawMenuButton(0, 4, 1, 1, - std::format("{} Moves ({}{} Time at this State)", - state.total_moves, visits, nth(visits)), - color); - - if (DrawMenuButton(1, 4, 1, 1, "Make Optimal Move (Space)", color, - !state.target_distances.Empty())) { - input.MakeOptimalMove(); - } - - if (DrawMenuButton(2, 4, 1, 1, "Undo Last Move (Backspace)", color, - state.history.size() > 0)) { - input.UndoLastMove(); - } - - if (DrawMenuButton(0, 5, 1, 1, "Go to Nearest Solution (B)", color, - !state.target_distances.Empty())) { - input.GoToNearestTarget(); - } - - if (DrawMenuButton(1, 5, 1, 1, "Go to Worst State (V)", color, - !state.target_distances.Empty())) { - input.GoToWorstState(); - } - - if (DrawMenuButton(2, 5, 1, 1, "Go to Starting State (R)", color)) { - input.ResetState(); - } -} - -auto Gui::DrawEditControls(Color color) const -> void { - // Toggle Target Block - if (DrawMenuButton(0, 4, 1, 1, "Toggle Target Block (T)", color)) { - input.ToggleTargetBlock(); - } - - // Toggle Wall Block - if (DrawMenuButton(0, 5, 1, 1, "Toggle Wall Block (Y)", color)) { - input.ToggleWallBlock(); - } - - // Toggle Restricted/Free Block Movement - int free = !state.current_state.restricted; - DrawMenuToggleSlider(1, 4, 1, 1, "Restricted (F)", "Free (F)", &free, color); - if (free != !state.current_state.restricted) { - input.ToggleRestrictedMovement(); - } - - // Clear Goal - if (DrawMenuButton(1, 5, 1, 1, "Clear Goal (X)", color)) { - } - - // Column Count Spinner - int columns = state.current_state.width; - DrawMenuSpinner(2, 4, 1, 1, "Cols: ", &columns, 1, 9, color); - if (columns > state.current_state.width) { - input.AddBoardColumn(); - } else if (columns < state.current_state.width) { - input.RemoveBoardColumn(); - } - - // Row Count Spinner - int rows = state.current_state.height; - DrawMenuSpinner(2, 5, 1, 1, "Rows: ", &rows, 1, 9, color); - if (rows > state.current_state.height) { - input.AddBoardRow(); - } else if (rows < state.current_state.height) { - input.RemoveBoardRow(); - } -} - -auto Gui::DrawMenuFooter(Color color) -> void { - DrawMenuButton(0, 6, 2, 1, - std::format("State: \"{}\"", state.current_state.state), - color); - - if (DrawMenuButton(2, 6, 1, 1, "Save as Preset", color)) { - save_window = true; - } -} - -auto Gui::GetBackgroundColor() const -> Color { - return GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)); -} - -auto Gui::HelpPopup() -> void {} - -auto Gui::DrawSavePresetPopup() -> void { - if (!save_window) { - return; - } - - int width = 450; - int height = 150; - // Returns the pressed button index - int button = GuiTextInputBox(Rectangle((GetScreenWidth() - width) / 2.0, - (GetScreenHeight() - height) / 2.0, - width, height), - "Save as Preset", "Enter Preset Name", - "Ok;Cancel", preset_name.data(), 255, NULL); - if (button == 1) { - state.AppendPresetFile(preset_name.data()); - } - if ((button == 0) || (button == 1) || (button == 2)) { - save_window = false; - TextCopy(preset_name.data(), "\0"); - } -} - -auto Gui::DrawMainMenu() -> void { - menu_grid.UpdateBounds(0, 0, GetScreenWidth(), MENU_HEIGHT); - - DrawMenuHeader(GRAY); - DrawGraphInfo(ORANGE); - DrawGraphControls(RED); - DrawCameraControls(DARKGREEN); - - if (input.editing) { - DrawEditControls(PURPLE); - } else { - DrawPuzzleControls(BLUE); - } - - DrawMenuFooter(GRAY); -} - -auto Gui::DrawPuzzleBoard() -> void { - board_grid.UpdateBounds( - 0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT, - state.current_state.width, state.current_state.height); - - // Draw outer border - Rectangle bounds = board_grid.SquareBounds(); - DrawRectangleRec(bounds, state.current_state.IsWon() - ? BOARD_COLOR_WON - : BOARD_COLOR_RESTRICTED); - - // Draw inner borders - DrawRectangle(bounds.x + BOARD_PADDING, bounds.y + BOARD_PADDING, - bounds.width - 2 * BOARD_PADDING, - bounds.height - 2 * BOARD_PADDING, - state.current_state.restricted ? BOARD_COLOR_RESTRICTED - : BOARD_COLOR_FREE); - - // Draw target opening - if (state.current_state.HasWinCondition()) { - int target_x = state.current_state.target_x; - int target_y = state.current_state.target_y; - Block target_block = state.current_state.GetTargetBlock(); - Rectangle target_bounds = board_grid.SquareBounds( - target_x, target_y, target_block.width, target_block.height); - - Color opening_color = Fade( - state.current_state.IsWon() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED, - 0.3); - - if (target_x == 0) { - - // Left opening - DrawRectangle(target_bounds.x - BOARD_PADDING, target_bounds.y, - BOARD_PADDING, target_bounds.height, RAYWHITE); - DrawRectangle(target_bounds.x - BOARD_PADDING, target_bounds.y, - BOARD_PADDING, target_bounds.height, opening_color); - } - if (target_x + target_block.width == state.current_state.width) { - - // Right opening - DrawRectangle(target_bounds.x + target_bounds.width, target_bounds.y, - BOARD_PADDING, target_bounds.height, RAYWHITE); - DrawRectangle(target_bounds.x + target_bounds.width, target_bounds.y, - BOARD_PADDING, target_bounds.height, opening_color); - } - if (target_y == 0) { - - // Top opening - DrawRectangle(target_bounds.x, target_bounds.y - BOARD_PADDING, - target_bounds.width, BOARD_PADDING, RAYWHITE); - DrawRectangle(target_bounds.x, target_bounds.y - BOARD_PADDING, - target_bounds.width, BOARD_PADDING, opening_color); - } - if (target_y + target_block.height == state.current_state.height) { - - // Bottom opening - DrawRectangle(target_bounds.x, target_bounds.y + target_bounds.height, - target_bounds.width, BOARD_PADDING, RAYWHITE); - DrawRectangle(target_bounds.x, target_bounds.y + target_bounds.height, - target_bounds.width, BOARD_PADDING, opening_color); - } - } - - // Draw empty cells. Also set hovered blocks - input.hov_x = -1; - input.hov_y = -1; - for (int x = 0; x < board_grid.columns; ++x) { - for (int y = 0; y < board_grid.rows; ++y) { - DrawRectangleRec(board_grid.SquareBounds(x, y, 1, 1), RAYWHITE); - - Rectangle hov_bounds = board_grid.SquareBounds(x, y, 1, 1); - hov_bounds.x -= BOARD_PADDING; - hov_bounds.y -= BOARD_PADDING; - hov_bounds.width += BOARD_PADDING; - hov_bounds.height += BOARD_PADDING; - if (CheckCollisionPointRec(GetMousePosition() - Vector2(0, MENU_HEIGHT), - hov_bounds)) { - input.hov_x = x; - input.hov_y = y; - } - } - } - - // Draw blocks - for (const Block &block : state.current_state) { - Color c = BLOCK_COLOR; - if (block.target) { - c = TARGET_BLOCK_COLOR; - } else if (block.immovable) { - c = WALL_COLOR; - } - - DrawBoardBlock(block.x, block.y, block.width, block.height, c, - !block.immovable); - } - - // Draw block placing - if (input.editing && input.has_block_add_xy) { - if (input.hov_x >= 0 && input.hov_x < state.current_state.width && - input.hov_y >= 0 && input.hov_y < state.current_state.height && - input.hov_x >= input.block_add_x && input.hov_y >= input.block_add_y) { - bool collides = false; - for (const Block &block : state.current_state) { - if (block.Collides(Block(input.block_add_x, input.block_add_y, - input.hov_x - input.block_add_x + 1, - input.hov_y - input.block_add_y + 1, false))) { - collides = true; - break; - } - } - if (!collides) { - DrawBoardBlock(input.block_add_x, input.block_add_y, - input.hov_x - input.block_add_x + 1, - input.hov_y - input.block_add_y + 1, PURPLE, false); - } - } - } -} - -auto Gui::DrawGraphOverlay(int fps, int ups) -> void { - graph_overlay_grid.UpdateBounds(GetScreenWidth() / 2, MENU_HEIGHT); - - DrawLabel(graph_overlay_grid.Bounds(0, 0, 1, 1), - std::format("Dist: {:0>7.2f}", camera.distance), BLACK); - DrawLabel(graph_overlay_grid.Bounds(0, 1, 1, 1), - std::format("FoV: {:0>6.2f}", camera.fov), BLACK); - DrawLabel(graph_overlay_grid.Bounds(0, 2, 1, 1), - std::format("FPS: {:0>3}", fps), LIME); - DrawLabel(graph_overlay_grid.Bounds(0, 3, 1, 1), - std::format("UPS: {:0>3}", ups), ORANGE); -} - -auto Gui::Update() -> void { input.disable = WindowOpen(); } diff --git a/src/input.cpp b/src/input.cpp index da5abe1..d93446d 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -3,494 +3,568 @@ #include -#ifdef TRACY -#include "tracy.hpp" -#include -#endif +auto input_handler::init_handlers() -> void +{ + // The order matters if multiple handlers are registered to the same key -auto InputHandler::InitHandlers() -> void { - // The order matters if multiple handlers are registered to the same key + register_generic_handler(&input_handler::camera_pan); + register_generic_handler(&input_handler::camera_rotate); + register_generic_handler(&input_handler::camera_zoom); + register_generic_handler(&input_handler::camera_fov); + register_generic_handler(&input_handler::mouse_hover); - RegisterGenericHandler(&InputHandler::CameraPan); - RegisterGenericHandler(&InputHandler::CameraRotate); - RegisterGenericHandler(&InputHandler::CameraZoom); - RegisterGenericHandler(&InputHandler::CameraFov); - RegisterGenericHandler(&InputHandler::MouseHover); + register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::camera_start_pan); + register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::select_block); + register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::add_block); + register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::start_add_block); + register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::camera_start_rotate); + register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::remove_block); + register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::clear_add_block); + register_mouse_pressed_handler(MOUSE_BUTTON_MIDDLE, &input_handler::place_goal); - RegisterMousePressedHandler(MOUSE_BUTTON_LEFT, &InputHandler::CameraStartPan); - RegisterMousePressedHandler(MOUSE_BUTTON_LEFT, &InputHandler::SelectBlock); - RegisterMousePressedHandler(MOUSE_BUTTON_LEFT, &InputHandler::AddBlock); - RegisterMousePressedHandler(MOUSE_BUTTON_LEFT, &InputHandler::StartAddBlock); - RegisterMousePressedHandler(MOUSE_BUTTON_RIGHT, - &InputHandler::CameraStartRotate); - RegisterMousePressedHandler(MOUSE_BUTTON_RIGHT, &InputHandler::RemoveBlock); - RegisterMousePressedHandler(MOUSE_BUTTON_RIGHT, &InputHandler::ClearAddBlock); - RegisterMousePressedHandler(MOUSE_BUTTON_MIDDLE, &InputHandler::PlaceGoal); + register_mouse_released_handler(MOUSE_BUTTON_LEFT, &input_handler::camera_stop_pan); + register_mouse_released_handler(MOUSE_BUTTON_RIGHT, &input_handler::camera_stop_rotate); - RegisterMouseReleasedHandler(MOUSE_BUTTON_LEFT, &InputHandler::CameraStopPan); - RegisterMouseReleasedHandler(MOUSE_BUTTON_RIGHT, - &InputHandler::CameraStopRotate); - - RegisterKeyPressedHandler(KEY_W, &InputHandler::MoveBlockNor); - RegisterKeyPressedHandler(KEY_D, &InputHandler::MoveBlockEas); - RegisterKeyPressedHandler(KEY_S, &InputHandler::MoveBlockSou); - RegisterKeyPressedHandler(KEY_A, &InputHandler::MoveBlockWes); - RegisterKeyPressedHandler(KEY_P, &InputHandler::PrintState); - RegisterKeyPressedHandler(KEY_N, &InputHandler::PreviousPreset); - RegisterKeyPressedHandler(KEY_M, &InputHandler::NextPreset); - RegisterKeyPressedHandler(KEY_R, &InputHandler::ResetState); - RegisterKeyPressedHandler(KEY_G, &InputHandler::FillGraph); - RegisterKeyPressedHandler(KEY_C, &InputHandler::ClearGraph); - RegisterKeyPressedHandler(KEY_I, &InputHandler::ToggleMarkSolutions); - RegisterKeyPressedHandler(KEY_O, &InputHandler::ToggleConnectSolutions); - // RegisterKeyPressedHandler(KEY_U, &InputHandler::ToggleMarkPath); - RegisterKeyPressedHandler(KEY_SPACE, &InputHandler::MakeOptimalMove); - RegisterKeyPressedHandler(KEY_V, &InputHandler::GoToWorstState); - RegisterKeyPressedHandler(KEY_B, &InputHandler::GoToNearestTarget); - RegisterKeyPressedHandler(KEY_BACKSPACE, &InputHandler::UndoLastMove); - RegisterKeyPressedHandler(KEY_F, &InputHandler::ToggleRestrictedMovement); - RegisterKeyPressedHandler(KEY_T, &InputHandler::ToggleTargetBlock); - RegisterKeyPressedHandler(KEY_Y, &InputHandler::ToggleWallBlock); - RegisterKeyPressedHandler(KEY_UP, &InputHandler::AddBoardRow); - RegisterKeyPressedHandler(KEY_RIGHT, &InputHandler::AddBoardColumn); - RegisterKeyPressedHandler(KEY_DOWN, &InputHandler::RemoveBoardRow); - RegisterKeyPressedHandler(KEY_LEFT, &InputHandler::RemoveBoardColumn); - RegisterKeyPressedHandler(KEY_TAB, &InputHandler::ToggleEditing); - RegisterKeyPressedHandler(KEY_L, &InputHandler::ToggleCameraLock); - RegisterKeyPressedHandler(KEY_LEFT_ALT, - &InputHandler::ToggleCameraProjection); - RegisterKeyPressedHandler(KEY_X, &InputHandler::ClearGoal); - RegisterKeyPressedHandler(KEY_Y, &InputHandler::ToggleCameraMassCenterLock); + register_key_pressed_handler(KEY_W, &input_handler::move_block_nor); + register_key_pressed_handler(KEY_D, &input_handler::move_block_eas); + register_key_pressed_handler(KEY_S, &input_handler::move_block_sou); + register_key_pressed_handler(KEY_A, &input_handler::move_block_wes); + register_key_pressed_handler(KEY_P, &input_handler::print_state); + register_key_pressed_handler(KEY_N, &input_handler::load_previous_preset); + register_key_pressed_handler(KEY_M, &input_handler::load_next_preset); + register_key_pressed_handler(KEY_R, &input_handler::goto_starting_state); + register_key_pressed_handler(KEY_G, &input_handler::populate_graph); + register_key_pressed_handler(KEY_C, &input_handler::clear_graph); + register_key_pressed_handler(KEY_I, &input_handler::toggle_mark_solutions); + register_key_pressed_handler(KEY_O, &input_handler::toggle_connect_solutions); + // RegisterKeyPressedHandler(KEY_U, &InputHandler::ToggleMarkPath); + register_key_pressed_handler(KEY_SPACE, &input_handler::goto_optimal_next_state); + register_key_pressed_handler(KEY_V, &input_handler::goto_most_distant_state); + register_key_pressed_handler(KEY_B, &input_handler::goto_closest_target_state); + register_key_pressed_handler(KEY_BACKSPACE, &input_handler::goto_previous_state); + register_key_pressed_handler(KEY_F, &input_handler::toggle_restricted_movement); + register_key_pressed_handler(KEY_T, &input_handler::toggle_target_block); + register_key_pressed_handler(KEY_Y, &input_handler::toggle_wall_block); + register_key_pressed_handler(KEY_UP, &input_handler::add_board_row); + register_key_pressed_handler(KEY_RIGHT, &input_handler::add_board_column); + register_key_pressed_handler(KEY_DOWN, &input_handler::remove_board_row); + register_key_pressed_handler(KEY_LEFT, &input_handler::remove_board_column); + register_key_pressed_handler(KEY_TAB, &input_handler::toggle_editing); + register_key_pressed_handler(KEY_L, &input_handler::toggle_camera_lock); + register_key_pressed_handler(KEY_LEFT_ALT, &input_handler::toggle_camera_projection); + register_key_pressed_handler(KEY_X, &input_handler::clear_goal); + register_key_pressed_handler(KEY_U, &input_handler::toggle_camera_mass_center_lock); } -auto InputHandler::MouseInMenuPane() -> bool { return mouse.y < MENU_HEIGHT; } - -auto InputHandler::MouseInBoardPane() -> bool { - return mouse.x < GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT; +auto input_handler::mouse_in_menu_pane() const -> bool +{ + return mouse.y < MENU_HEIGHT; } -auto InputHandler::MouseInGraphPane() -> bool { - return mouse.x >= GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT; +auto input_handler::mouse_in_board_pane() const -> bool +{ + return mouse.x < GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT; } -auto InputHandler::MouseHover() -> void { - last_mouse = mouse; - mouse = GetMousePosition(); +auto input_handler::mouse_in_graph_pane() const -> bool +{ + return mouse.x >= GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT; } -auto InputHandler::CameraStartPan() -> void { - if (!MouseInGraphPane()) { - return; - } - - camera_panning = true; - // Enable this if the camera should be pannable even when locked (releasing - // the lock in the process): - // camera_lock = false; +auto input_handler::mouse_hover() -> void +{ + last_mouse = mouse; + mouse = GetMousePosition(); } -auto InputHandler::CameraPan() -> void { - if (camera_panning) { - camera.Pan(last_mouse, mouse); - } +auto input_handler::camera_start_pan() -> void +{ + if (!mouse_in_graph_pane()) { + return; + } + + camera_panning = true; + // Enable this if the camera should be pannable even when locked (releasing the lock in the process): + // camera_lock = false; } -auto InputHandler::CameraStopPan() -> void { camera_panning = false; } - -auto InputHandler::CameraStartRotate() -> void { - if (!MouseInGraphPane()) { - return; - } - - camera_rotating = true; +auto input_handler::camera_pan() const -> void +{ + if (camera_panning) { + camera.pan(last_mouse, mouse); + } } -auto InputHandler::CameraRotate() -> void { - if (camera_rotating) { - camera.Rotate(last_mouse, mouse); - } -} - -auto InputHandler::CameraStopRotate() -> void { camera_rotating = false; } - -auto InputHandler::CameraZoom() -> void { - if (!MouseInGraphPane() || IsKeyDown(KEY_LEFT_CONTROL) || - camera.projection == CAMERA_ORTHOGRAPHIC) { - return; - } - - float wheel = GetMouseWheelMove(); - - if (IsKeyDown(KEY_LEFT_SHIFT)) { - camera.distance -= wheel * ZOOM_SPEED * ZOOM_MULTIPLIER; - } else { - camera.distance -= wheel * ZOOM_SPEED; - } -} - -auto InputHandler::CameraFov() -> void { - if (!MouseInGraphPane() || !IsKeyDown(KEY_LEFT_CONTROL) || - IsKeyDown(KEY_LEFT_SHIFT)) { - return; - } - - float wheel = GetMouseWheelMove(); - camera.fov -= wheel * FOV_SPEED; -} - -auto InputHandler::ToggleCameraLock() -> void { - if (!camera_lock) { +auto input_handler::camera_stop_pan() -> void +{ camera_panning = false; - } - - camera_lock = !camera_lock; } -auto InputHandler::ToggleCameraMassCenterLock() -> void { - if (!camera_mass_center_lock) { - camera_lock = true; - camera_panning = false; - } +auto input_handler::camera_start_rotate() -> void +{ + if (!mouse_in_graph_pane()) { + return; + } - camera_mass_center_lock = !camera_mass_center_lock; + camera_rotating = true; } -auto InputHandler::ToggleCameraProjection() -> void { - camera.projection = camera.projection == CAMERA_PERSPECTIVE - ? CAMERA_ORTHOGRAPHIC - : CAMERA_PERSPECTIVE; +auto input_handler::camera_rotate() const -> void +{ + if (camera_rotating) { + camera.rotate(last_mouse, mouse); + } } -auto InputHandler::SelectBlock() -> void { - if (state.current_state.GetBlock(hov_x, hov_y).IsValid()) { - sel_x = hov_x; - sel_y = hov_y; - } +auto input_handler::camera_stop_rotate() -> void +{ + camera_rotating = false; } -auto InputHandler::StartAddBlock() -> void { - if (!editing || state.current_state.GetBlock(hov_x, hov_y).IsValid() || - has_block_add_xy) { - return; - } +auto input_handler::camera_zoom() const -> void +{ + if (!mouse_in_graph_pane() || IsKeyDown(KEY_LEFT_CONTROL) || camera.projection == CAMERA_ORTHOGRAPHIC) { + return; + } - if (hov_x >= 0 && hov_x < state.current_state.width && hov_y >= 0 && - hov_y < state.current_state.height) { - block_add_x = hov_x; - block_add_y = hov_y; - has_block_add_xy = true; - } + const float wheel = GetMouseWheelMove(); + + if (IsKeyDown(KEY_LEFT_SHIFT)) { + camera.distance -= wheel * ZOOM_SPEED * ZOOM_MULTIPLIER; + } else { + camera.distance -= wheel * ZOOM_SPEED; + } } -auto InputHandler::ClearAddBlock() -> void { - if (!editing || !has_block_add_xy) { - return; - } +auto input_handler::camera_fov() const -> void +{ + if (!mouse_in_graph_pane() || !IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT)) { + return; + } - block_add_x = -1; - block_add_y = -1; - has_block_add_xy = false; + const float wheel = GetMouseWheelMove(); + camera.fov -= wheel * FOV_SPEED; } -auto InputHandler::AddBlock() -> void { - if (!editing || state.current_state.GetBlock(hov_x, hov_y).IsValid() || - !has_block_add_xy) { - return; - } +auto input_handler::select_block() -> void +{ + const puzzle& current = state.get_current_state(); + if (current.try_get_block(hov_x, hov_y)) { + sel_x = hov_x; + sel_y = hov_y; + } +} + +auto input_handler::start_add_block() -> void +{ + const puzzle& current = state.get_current_state(); + if (!editing || current.try_get_block(hov_x, hov_y) || has_block_add_xy) { + return; + } + + if (hov_x >= 0 && hov_x < current.width && hov_y >= 0 && hov_y < current.height) { + block_add_x = hov_x; + block_add_y = hov_y; + has_block_add_xy = true; + } +} + +auto input_handler::clear_add_block() -> void +{ + if (!editing || !has_block_add_xy) { + return; + } - int block_add_width = hov_x - block_add_x + 1; - int block_add_height = hov_y - block_add_y + 1; - if (block_add_width <= 0 || block_add_height <= 0) { block_add_x = -1; block_add_y = -1; has_block_add_xy = false; - } else if (block_add_x >= 0 && - block_add_x + block_add_width <= state.current_state.width && - block_add_y >= 0 && - block_add_y + block_add_height <= state.current_state.height) { - bool success = state.current_state.AddBlock(Block( - block_add_x, block_add_y, block_add_width, block_add_height, false)); - - if (success) { - sel_x = block_add_x; - sel_y = block_add_y; - block_add_x = -1; - block_add_y = -1; - has_block_add_xy = false; - state.ClearGraph(); - state.edited = true; - } - } } -auto InputHandler::RemoveBlock() -> void { - Block block = state.current_state.GetBlock(hov_x, hov_y); - if (!editing || has_block_add_xy || !block.IsValid() || - !state.current_state.RemoveBlock(hov_x, hov_y)) { - return; - } +auto input_handler::add_block() -> void +{ + const puzzle& current = state.get_current_state(); + if (!editing || current.try_get_block(hov_x, hov_y) || !has_block_add_xy) { + return; + } - if (block.Covers(sel_x, sel_y)) { + const int block_add_width = hov_x - block_add_x + 1; + const int block_add_height = hov_y - block_add_y + 1; + if (block_add_width <= 0 || block_add_height <= 0) { + block_add_x = -1; + block_add_y = -1; + has_block_add_xy = false; + } else if (current.covers(block_add_x, block_add_y, block_add_width, block_add_height)) { + const std::optional& next = current.try_add_block( + puzzle::block(block_add_x, block_add_y, block_add_width, block_add_height, false)); + + if (next) { + sel_x = block_add_x; + sel_y = block_add_y; + block_add_x = -1; + block_add_y = -1; + has_block_add_xy = false; + state.edit_starting_state(*next); + } + } +} + +auto input_handler::remove_block() -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& b = current.try_get_block(hov_x, hov_y); + if (!editing || has_block_add_xy || !b) { + return; + } + + const std::optional& next = current.try_remove_block(hov_x, hov_y); + if (!next) { + return; + } + + // Reset selection if we removed the selected block + if (b->covers(sel_x, sel_y)) { + sel_x = 0; + sel_y = 0; + } + + state.edit_starting_state(*next); +} + +auto input_handler::place_goal() const -> void +{ + const puzzle& current = state.get_current_state(); + if (!editing || !current.covers(hov_x, hov_y)) { + return; + } + + const std::optional& next = current.try_set_goal(hov_x, hov_y); + if (!next) { + return; + } + + state.edit_starting_state(*next); +} + +auto input_handler::toggle_camera_lock() -> void +{ + if (!camera_lock) { + camera_panning = false; + } + + camera_lock = !camera_lock; +} + +auto input_handler::toggle_camera_mass_center_lock() -> void +{ + if (!camera_mass_center_lock) { + camera_lock = true; + camera_panning = false; + } + + camera_mass_center_lock = !camera_mass_center_lock; +} + +auto input_handler::toggle_camera_projection() const -> void +{ + camera.projection = camera.projection == CAMERA_PERSPECTIVE ? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; +} + +auto input_handler::move_block_nor() -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_move_block_at(sel_x, sel_y, nor); + if (!next) { + return; + } + + sel_y--; + state.update_current_state(*next); +} + +auto input_handler::move_block_wes() -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_move_block_at(sel_x, sel_y, wes); + if (!next) { + return; + } + + sel_x--; + state.update_current_state(*next); +} + +auto input_handler::move_block_sou() -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_move_block_at(sel_x, sel_y, sou); + if (!next) { + return; + } + + sel_y++; + state.update_current_state(*next); +} + +auto input_handler::move_block_eas() -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_move_block_at(sel_x, sel_y, eas); + if (!next) { + return; + } + + sel_x++; + state.update_current_state(*next); +} + +auto input_handler::print_state() const -> void +{ + infoln("State: \"{}\"", state.get_current_state().state); +} + +auto input_handler::load_previous_preset() -> void +{ + if (editing) { + return; + } + + block_add_x = -1; + block_add_y = -1; + has_block_add_xy = false; + state.load_previous_preset(); +} + +auto input_handler::load_next_preset() -> void +{ + if (editing) { + return; + } + + block_add_x = -1; + block_add_y = -1; + has_block_add_xy = false; + state.load_next_preset(); +} + +auto input_handler::goto_starting_state() -> void +{ + state.goto_starting_state(); sel_x = 0; sel_y = 0; - } - - state.ClearGraph(); - state.edited = true; } -auto InputHandler::PlaceGoal() -> void { - if (!editing || hov_x < 0 || hov_x >= state.current_state.width || - hov_y < 0 || hov_y >= state.current_state.height) { - return; - } - - if (state.current_state.SetGoal(hov_x, hov_y)) { - // We can't just call state.FindWinningStates() because the - // state is entirely different if it has a different win condition. - state.ClearGraph(); - } +auto input_handler::populate_graph() const -> void +{ + state.populate_graph(); } -auto InputHandler::MoveBlockNor() -> void { - if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::NOR)) { - sel_y--; - } +auto input_handler::clear_graph() const -> void +{ + state.clear_graph_and_add_current(); } -auto InputHandler::MoveBlockEas() -> void { - if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::EAS)) { - sel_x++; - } +auto input_handler::toggle_mark_solutions() -> void +{ + mark_solutions = !mark_solutions; } -auto InputHandler::MoveBlockSou() -> void { - if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::SOU)) { - sel_y++; - } +auto input_handler::toggle_connect_solutions() -> void +{ + connect_solutions = !connect_solutions; } -auto InputHandler::MoveBlockWes() -> void { - if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::WES)) { - sel_x--; - } +auto input_handler::toggle_mark_path() -> void +{ + mark_path = !mark_path; } -auto InputHandler::PrintState() const -> void { - std::cout << std::format("State: \"{}\"", state.current_state.state) - << std::endl; - Block sel = state.current_state.GetBlock(sel_x, sel_y); - int idx = state.current_state.GetIndex(sel.x, sel.y) - State::prefix; - if (sel.IsValid()) { - std::cout << std::format("Sel: \"{}{}{}{}\"", - state.current_state.state.substr(0, State::prefix), - std::string(idx, '.'), sel.ToString(), - std::string(state.current_state.state.length() - - idx - State::prefix - 2, - '.')) - << std::endl; - } +auto input_handler::goto_optimal_next_state() const -> void +{ + state.goto_optimal_next_state(); } -auto InputHandler::PreviousPreset() -> void { - if (editing) { - return; - } - - block_add_x = -1; - block_add_y = -1; - has_block_add_xy = false; - state.PreviousPreset(); +auto input_handler::goto_most_distant_state() const -> void +{ + state.goto_most_distant_state(); } -auto InputHandler::NextPreset() -> void { - if (editing) { - return; - } - - block_add_x = -1; - block_add_y = -1; - has_block_add_xy = false; - state.NextPreset(); +auto input_handler::goto_closest_target_state() const -> void +{ + state.goto_closest_target_state(); } -auto InputHandler::ResetState() -> void { - if (editing) { - return; - } - - state.ResetState(); - sel_x = 0; - sel_y = 0; +auto input_handler::goto_previous_state() const -> void +{ + state.goto_previous_state(); } -auto InputHandler::FillGraph() -> void { state.FillGraph(); } +auto input_handler::toggle_restricted_movement() const -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_toggle_restricted(); -auto InputHandler::ClearGraph() -> void { state.ClearGraph(); } - -auto InputHandler::ToggleMarkSolutions() -> void { - mark_solutions = !mark_solutions; -} - -auto InputHandler::ToggleConnectSolutions() -> void { - connect_solutions = !connect_solutions; -} - -auto InputHandler::ToggleMarkPath() -> void { mark_path = !mark_path; } - -auto InputHandler::MakeOptimalMove() -> void { state.NextPath(); } - -auto InputHandler::GoToWorstState() -> void { state.GoToWorst(); } - -auto InputHandler::GoToNearestTarget() -> void { state.GoToNearestTarget(); } - -auto InputHandler::UndoLastMove() -> void { state.PopHistory(); } - -auto InputHandler::ToggleRestrictedMovement() -> void { - if (!editing) { - return; - } - - state.current_state.ToggleRestricted(); - state.ClearGraph(); - state.edited = true; -} - -auto InputHandler::ToggleTargetBlock() -> void { - if (!editing) { - return; - } - - state.current_state.ToggleTarget(sel_x, sel_y); - state.ClearGraph(); - state.edited = true; -} - -auto InputHandler::ToggleWallBlock() -> void { - if (!editing) { - return; - } - - state.current_state.ToggleWall(sel_x, sel_y); - state.ClearGraph(); - state.edited = true; -} - -auto InputHandler::RemoveBoardColumn() -> void { - if (!editing || state.current_state.width <= 1) { - return; - } - - state.current_state = state.current_state.RemoveColumn(); - state.ClearGraph(); - state.edited = true; -} - -auto InputHandler::AddBoardColumn() -> void { - if (!editing || state.current_state.width >= 9) { - return; - } - - state.current_state = state.current_state.AddColumn(); - state.ClearGraph(); - state.edited = true; -} - -auto InputHandler::RemoveBoardRow() -> void { - if (!editing || state.current_state.height <= 1) { - return; - } - - state.current_state = state.current_state.RemoveRow(); - state.ClearGraph(); - state.edited = true; -} - -auto InputHandler::AddBoardRow() -> void { - if (!editing || state.current_state.height >= 9) { - return; - } - - state.current_state = state.current_state.AddRow(); - state.ClearGraph(); - state.edited = true; -} - -auto InputHandler::ToggleEditing() -> void { - if (editing) { - has_block_add_xy = false; - block_add_x = -1; - block_add_y = -1; - } - - editing = !editing; -} - -auto InputHandler::ClearGoal() -> void { - if (!editing) { - return; - } - - state.current_state.ClearGoal(); - state.ClearGraph(); - state.edited = true; -} - -auto InputHandler::RegisterGenericHandler( - std::function handler) -> void { - generic_handlers.push_back({handler}); -} - -auto InputHandler::RegisterMousePressedHandler( - MouseButton button, std::function handler) -> void { - mouse_pressed_handlers.push_back({handler, button}); -} - -auto InputHandler::RegisterMouseReleasedHandler( - MouseButton button, std::function handler) -> void { - mouse_released_handlers.push_back({handler, button}); -} - -auto InputHandler::RegisterKeyPressedHandler( - KeyboardKey key, std::function handler) -> void { - key_pressed_handlers.push_back({handler, key}); -} - -auto InputHandler::RegisterKeyReleasedHandler( - KeyboardKey key, std::function handler) -> void { - key_released_handlers.push_back({handler, key}); -} - -auto InputHandler::HandleInput() -> void { - if (disable) { - return; - } - - for (const GenericHandler &handler : generic_handlers) { - handler.handler(*this); - } - - for (const MouseHandler &handler : mouse_pressed_handlers) { - if (IsMouseButtonPressed(handler.button)) { - handler.handler(*this); + if (!editing || !next) { + return; } - } - for (const MouseHandler &handler : mouse_released_handlers) { - if (IsMouseButtonReleased(handler.button)) { - handler.handler(*this); - } - } - - for (const KeyboardHandler &handler : key_pressed_handlers) { - if (IsKeyPressed(handler.key)) { - handler.handler(*this); - } - } - - for (const KeyboardHandler &handler : key_released_handlers) { - if (IsKeyReleased(handler.key)) { - handler.handler(*this); - } - } + state.edit_starting_state(*next); } + +auto input_handler::toggle_target_block() const -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_toggle_target(sel_x, sel_y); + + if (!editing || !next) { + return; + } + + state.edit_starting_state(*next); +} + +auto input_handler::toggle_wall_block() const -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_toggle_wall(sel_x, sel_y); + + if (!editing || !next) { + return; + } + + state.edit_starting_state(*next); +} + +auto input_handler::remove_board_column() const -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_remove_column(); + + if (!editing || current.width <= puzzle::MIN_WIDTH || !next) { + return; + } + + state.edit_starting_state(*next); +} + +auto input_handler::add_board_column() const -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_add_column(); + + if (!editing || current.width >= puzzle::MAX_WIDTH || !next) { + return; + } + + state.edit_starting_state(*next); +} + +auto input_handler::remove_board_row() const -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_remove_row(); + + if (!editing || current.height <= puzzle::MIN_HEIGHT || !next) { + return; + } + + state.edit_starting_state(*next); +} + +auto input_handler::add_board_row() const -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_add_row(); + + if (!editing || current.height >= puzzle::MAX_HEIGHT || !next) { + return; + } + + state.edit_starting_state(*next); +} + +auto input_handler::toggle_editing() -> void +{ + if (editing) { + has_block_add_xy = false; + block_add_x = -1; + block_add_y = -1; + } + + editing = !editing; +} + +auto input_handler::clear_goal() const -> void +{ + const puzzle& current = state.get_current_state(); + const std::optional& next = current.try_clear_goal(); + + if (!editing || !next) { + return; + } + + state.edit_starting_state(*next); +} + +auto input_handler::register_generic_handler(const std::function& handler) -> void +{ + generic_handlers.push_back({handler}); +} + +auto input_handler::register_mouse_pressed_handler(const MouseButton button, + const std::function& handler) -> void +{ + mouse_pressed_handlers.push_back({{handler}, button}); +} + +auto input_handler::register_mouse_released_handler(const MouseButton button, + const std::function& handler) -> void +{ + mouse_released_handlers.push_back({{handler}, button}); +} + +auto input_handler::register_key_pressed_handler(const KeyboardKey key, + const std::function& handler) -> void +{ + key_pressed_handlers.push_back({{handler}, key}); +} + +auto input_handler::register_key_released_handler(const KeyboardKey key, + const std::function& handler) -> void +{ + key_released_handlers.push_back({{handler}, key}); +} + +auto input_handler::handle_input() -> void +{ + if (disable) { + return; + } + + for (const auto& [handler] : generic_handlers) { + handler(*this); + } + + for (const mouse_handler& handler : mouse_pressed_handlers) { + if (IsMouseButtonPressed(handler.button)) { + handler.handler(*this); + } + } + + for (const mouse_handler& handler : mouse_released_handlers) { + if (IsMouseButtonReleased(handler.button)) { + handler.handler(*this); + } + } + + for (const keyboard_handler& handler : key_pressed_handlers) { + if (IsKeyPressed(handler.key)) { + handler.handler(*this); + } + } + + for (const keyboard_handler& handler : key_released_handlers) { + if (IsKeyReleased(handler.key)) { + handler.handler(*this); + } + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 794a9e5..fb39280 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,15 @@ #include #include #include -#include #include "config.hpp" -#include "gui.hpp" +#include "user_interface.hpp" #include "input.hpp" #include "physics.hpp" #include "renderer.hpp" -#include "state.hpp" +#include "state_manager.hpp" #ifdef TRACY -#include "tracy.hpp" #include #endif @@ -44,7 +42,8 @@ // NOTE: Tracy uses a huge amount of memory. For longer testing disable Tracy. -auto main(int argc, char *argv[]) -> int { +auto main(int argc, char* argv[]) -> int +{ std::string preset_file; if (argc != 2) { preset_file = "default.puzzle"; @@ -52,17 +51,17 @@ auto main(int argc, char *argv[]) -> int { preset_file = argv[1]; } -#ifdef BACKWARD - std::cout << std::format("Backward stack-traces enabled.") << std::endl; -#else - std::cout << std::format("Backward stack-traces disabled.") << std::endl; -#endif + #ifdef BACKWARD + infoln("Backward stack-traces enabled."); + #else + infoln("Backward stack-traces disabled."); + #endif -#ifdef TRACY - std::cout << std::format("Tracy adapter enabled.") << std::endl; -#else - std::cout << std::format("Tracy adapter disabled.") << std::endl; -#endif + #ifdef TRACY + infoln("Tracy adapter enabled."); + #else + infoln("Tracy adapter disabled."); + #endif // RayLib window setup SetTraceLogLevel(LOG_ERROR); @@ -73,51 +72,54 @@ auto main(int argc, char *argv[]) -> int { InitWindow(INITIAL_WIDTH * 2, INITIAL_HEIGHT + MENU_HEIGHT, "MassSprings"); // Game setup - ThreadedPhysics physics; - StateManager state(physics, preset_file); - OrbitCamera3D camera; - InputHandler input(state, camera); - Gui gui(input, state, camera); - Renderer renderer(camera, state, input, gui); + threaded_physics physics; + state_manager state(physics, preset_file); + orbit_camera camera; + input_handler input(state, camera); + user_interface gui(input, state, camera); + renderer renderer(camera, state, input, gui); std::chrono::time_point last = std::chrono::high_resolution_clock::now(); std::chrono::duration fps_accumulator(0); - unsigned int loop_iterations = 0; + int loop_iterations = 0; - unsigned int fps = 0; - unsigned int ups = 0; // Read from physics - Vector3 mass_center = Vector3Zero(); // Read from physics - std::vector masses; // Read from physics + int fps = 0; + int ups = 0; // Read from physics + Vector3 mass_center; // Read from physics + std::vector masses; // Read from physics + size_t mass_count = 0; + size_t spring_count = 0; // Game loop while (!WindowShouldClose()) { -#ifdef TRACY + #ifdef TRACY FrameMarkStart("MainThread"); -#endif + #endif // Time tracking std::chrono::time_point now = std::chrono::high_resolution_clock::now(); - std::chrono::duration deltatime = now - last; - fps_accumulator += deltatime; + std::chrono::duration delta_time = now - last; + fps_accumulator += delta_time; last = now; // Input update - input.HandleInput(); - state.UpdateGraph(); // Add state added after user input + input.handle_input(); // Read positions from physics thread -#ifdef TRACY + #ifdef TRACY FrameMarkStart("MainThreadConsumeLock"); -#endif + #endif { -#ifdef TRACY + #ifdef TRACY std::unique_lock lock(physics.state.data_mtx); -#else + #else std::unique_lock lock(physics.state.data_mtx); -#endif + #endif ups = physics.state.ups; mass_center = physics.state.mass_center; + mass_count = physics.state.mass_count; + spring_count = physics.state.spring_count; // Only copy data if any has been produced if (physics.state.data_ready) { @@ -131,24 +133,19 @@ auto main(int argc, char *argv[]) -> int { physics.state.data_consumed_cnd.notify_all(); } } -#ifdef TRACY + #ifdef TRACY FrameMarkEnd("MainThreadConsumeLock"); -#endif + #endif // Update the camera after the physics, so target lock is smooth - std::size_t current_index = state.CurrentMassIndex(); + size_t current_index = state.get_current_index(); if (masses.size() > current_index) { - const Mass ¤t_mass = masses.at(current_index); - camera.Update(current_mass.position, mass_center, input.camera_lock, - input.camera_mass_center_lock); + const mass& current_mass = mass(masses.at(current_index)); + camera.update(current_mass.position, mass_center, input.camera_lock, input.camera_mass_center_lock); } // Rendering - renderer.UpdateTextureSizes(); - renderer.DrawMassSprings(masses); - renderer.DrawKlotski(); - renderer.DrawMenu(masses); - renderer.DrawTextures(fps, ups); + renderer.render(masses, fps, ups, mass_count, spring_count); if (fps_accumulator.count() > 1.0) { // Update each second @@ -158,13 +155,13 @@ auto main(int argc, char *argv[]) -> int { } ++loop_iterations; -#ifdef TRACY + #ifdef TRACY FrameMark; FrameMarkEnd("MainThread"); -#endif + #endif } CloseWindow(); return 0; -} +} \ No newline at end of file diff --git a/src/octree.cpp b/src/octree.cpp index 6ac4580..4351598 100644 --- a/src/octree.cpp +++ b/src/octree.cpp @@ -1,168 +1,195 @@ #include "octree.hpp" #include "config.hpp" +#include "util.hpp" #include #ifdef TRACY -#include "tracy.hpp" #include #endif -auto OctreeNode::ChildCount() const -> int { - int child_count = 0; - for (int child : children) { - if (child != -1) { - ++child_count; +auto octree::node::child_count() const -> int +{ + int child_count = 0; + for (const int child : children) { + if (child != -1) { + ++child_count; + } } - } - return child_count; + return child_count; } -auto Octree::CreateNode(const Vector3 &box_min, const Vector3 &box_max) -> int { - OctreeNode node; - node.box_min = box_min; - node.box_max = box_max; - nodes.push_back(node); +auto octree::create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int +{ + node n; + n.box_min = box_min; + n.box_max = box_max; + nodes.emplace_back(n); - return nodes.size() - 1; + return static_cast(nodes.size() - 1); } -auto Octree::GetOctant(int node_idx, const Vector3 &pos) -> int { - OctreeNode &node = nodes[node_idx]; - Vector3 center = Vector3((node.box_min.x + node.box_max.x) / 2.0, - (node.box_min.y + node.box_max.y) / 2.0, - (node.box_min.z + node.box_max.z) / 2.0); +auto octree::get_octant(const int node_idx, const Vector3& pos) const -> int +{ + const node& n = nodes[node_idx]; + auto [cx, cy, cz] = Vector3((n.box_min.x + n.box_max.x) / 2.0f, (n.box_min.y + n.box_max.y) / 2.0f, + (n.box_min.z + n.box_max.z) / 2.0f); - // The octant is encoded as a 3-bit integer "zyx". The node area is split - // along all 3 axes, if a position is right of an axis, this bit is set to 1. - // If a position is right of the x-axis and y-axis and left of the z-axis, the - // encoded octant is "011". - int octant = 0; - if (pos.x >= center.x) { - octant |= 1; - } - if (pos.y >= center.y) { - octant |= 2; - } - if (pos.z >= center.z) { - octant |= 4; - } + // The octant is encoded as a 3-bit integer "zyx". The node area is split + // along all 3 axes, if a position is right of an axis, this bit is set to 1. + // If a position is right of the x-axis and y-axis and left of the z-axis, the + // encoded octant is "011". + int octant = 0; + if (pos.x >= cx) { + octant |= 1; + } + if (pos.y >= cy) { + octant |= 2; + } + if (pos.z >= cz) { + octant |= 4; + } - return octant; + return octant; } -auto Octree::GetChildBounds(int node_idx, int octant) - -> std::pair { - OctreeNode &node = nodes[node_idx]; - Vector3 center = Vector3((node.box_min.x + node.box_max.x) / 2.0, - (node.box_min.y + node.box_max.y) / 2.0, - (node.box_min.z + node.box_max.z) / 2.0); +auto octree::get_child_bounds(const int node_idx, const int octant) const -> std::pair +{ + const node& n = nodes[node_idx]; + auto [cx, cy, cz] = Vector3((n.box_min.x + n.box_max.x) / 2.0f, (n.box_min.y + n.box_max.y) / 2.0f, + (n.box_min.z + n.box_max.z) / 2.0f); - Vector3 min = Vector3Zero(); - Vector3 max = Vector3Zero(); + Vector3 min = Vector3Zero(); + Vector3 max = Vector3Zero(); - // If (octant & 1), the octant is to the right of the node region's x-axis - // (see GetOctant). This means the left bound is the x-axis and the right - // bound the node's region max. - min.x = (octant & 1) ? center.x : node.box_min.x; - max.x = (octant & 1) ? node.box_max.x : center.x; - min.y = (octant & 2) ? center.y : node.box_min.y; - max.y = (octant & 2) ? node.box_max.y : center.y; - min.z = (octant & 4) ? center.z : node.box_min.z; - max.z = (octant & 4) ? node.box_max.z : center.z; + // If (octant & 1), the octant is to the right of the node region's x-axis + // (see GetOctant). This means the left bound is the x-axis and the right + // bound the node's region max. + min.x = octant & 1 ? cx : n.box_min.x; + max.x = octant & 1 ? n.box_max.x : cx; + min.y = octant & 2 ? cy : n.box_min.y; + max.y = octant & 2 ? n.box_max.y : cy; + min.z = octant & 4 ? cz : n.box_min.z; + max.z = octant & 4 ? n.box_max.z : cz; - return std::make_pair(min, max); + return std::make_pair(min, max); } -auto Octree::Insert(int node_idx, int mass_id, const Vector3 &pos, float mass) - -> void { - // NOTE: Do not store a nodes[node_idx] reference beforehand as the nodes - // vector might reallocate during this function +auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, const float mass, + const int depth) -> void +{ + // infoln("Inserting position ({}, {}, {}) into octree at node {} (depth {})", pos.x, pos.y, pos.z, node_idx, depth); + if (depth > MAX_DEPTH) { + errln("MAX_DEPTH! node={} box_min=({},{},{}) box_max=({},{},{}) pos=({},{},{})", + node_idx, + nodes[node_idx].box_min.x, nodes[node_idx].box_min.y, nodes[node_idx].box_min.z, + nodes[node_idx].box_max.x, nodes[node_idx].box_max.y, nodes[node_idx].box_max.z, + pos.x, pos.y, pos.z); + + // This runs from inside the physics thread so it won't exit cleanly + exit(1); + } + + // NOTE: Do not store a nodes[node_idx] reference as the nodes vector might reallocate during this function - if (nodes[node_idx].leaf && nodes[node_idx].mass_id == -1) { // We can place the particle in the empty leaf - nodes[node_idx].mass_id = mass_id; - nodes[node_idx].mass_center = pos; - nodes[node_idx].mass_total = mass; - return; - } + if (nodes[node_idx].leaf && nodes[node_idx].mass_id == -1) { + nodes[node_idx].mass_id = mass_id; + nodes[node_idx].mass_center = pos; + nodes[node_idx].mass_total = mass; + return; + } - if (nodes[node_idx].leaf) { // The leaf is occupied, we need to subdivide - int existing_id = nodes[node_idx].mass_id; - Vector3 existing_pos = nodes[node_idx].mass_center; - float existing_mass = nodes[node_idx].mass_total; - nodes[node_idx].mass_id = -1; - nodes[node_idx].leaf = false; - nodes[node_idx].mass_total = 0.0; + if (nodes[node_idx].leaf) { + const int existing_id = nodes[node_idx].mass_id; + const Vector3 existing_pos = nodes[node_idx].mass_center; + const float existing_mass = nodes[node_idx].mass_total; - // Re-insert the existing mass into a new empty leaf (see above) - int oct = GetOctant(node_idx, existing_pos); + // If positions are identical we jitter the particles + const Vector3 diff = Vector3Subtract(pos, existing_pos); + if (diff == Vector3Zero()) { + // warnln("Trying to insert an identical partical into octree (jittering position)"); + + Vector3 jittered = pos; + jittered.x += 0.001; + jittered.y += 0.001; + insert(node_idx, mass_id, jittered, mass, depth); + return; + + // Could also merge them, but that leads to the octree having less leafs than we have masses + // nodes[node_idx].mass_total += mass; + // return; + } + + // Convert the leaf to an internal node + nodes[node_idx].mass_id = -1; + nodes[node_idx].leaf = false; + nodes[node_idx].mass_total = 0.0; + nodes[node_idx].mass_center = Vector3Zero(); + + // Re-insert the existing mass into a new empty leaf (see above) + const int oct = get_octant(node_idx, existing_pos); + if (nodes[node_idx].children[oct] == -1) { + const auto &[min, max] = get_child_bounds(node_idx, oct); + const int child_idx = create_empty_leaf(min, max); + nodes[node_idx].children[oct] = child_idx; + } + insert(nodes[node_idx].children[oct], existing_id, existing_pos, existing_mass, depth + 1); + } + + // Insert the new mass + const int oct = get_octant(node_idx, pos); if (nodes[node_idx].children[oct] == -1) { - auto [min, max] = GetChildBounds(node_idx, oct); - nodes[node_idx].children[oct] = CreateNode(min, max); + const auto &[min, max] = get_child_bounds(node_idx, oct); + const int child_idx = create_empty_leaf(min, max); + nodes[node_idx].children[oct] = child_idx; } - Insert(nodes[node_idx].children[oct], existing_id, existing_pos, - existing_mass); - } + insert(nodes[node_idx].children[oct], mass_id, pos, mass, depth + 1); - // Insert the new mass - int oct = GetOctant(node_idx, pos); - if (nodes[node_idx].children[oct] == -1) { - auto [min, max] = GetChildBounds(node_idx, oct); - nodes[node_idx].children[oct] = CreateNode(min, max); - } - Insert(nodes[node_idx].children[oct], mass_id, pos, mass); - - // Update the center of mass - float new_mass = nodes[node_idx].mass_total + mass; - nodes[node_idx].mass_center.x = - (nodes[node_idx].mass_center.x * nodes[node_idx].mass_total + pos.x) / - new_mass; - nodes[node_idx].mass_center.y = - (nodes[node_idx].mass_center.y * nodes[node_idx].mass_total + pos.y) / - new_mass; - nodes[node_idx].mass_center.z = - (nodes[node_idx].mass_center.z * nodes[node_idx].mass_total + pos.z) / - new_mass; - nodes[node_idx].mass_total = new_mass; + // Update the center of mass + const float new_mass = nodes[node_idx].mass_total + mass; + nodes[node_idx].mass_center.x = (nodes[node_idx].mass_center.x * nodes[node_idx].mass_total + pos.x) / new_mass; + nodes[node_idx].mass_center.y = (nodes[node_idx].mass_center.y * nodes[node_idx].mass_total + pos.y) / new_mass; + nodes[node_idx].mass_center.z = (nodes[node_idx].mass_center.z * nodes[node_idx].mass_total + pos.z) / new_mass; + nodes[node_idx].mass_total = new_mass; } -auto Octree::CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3 { - if (node_idx < 0) { - return Vector3Zero(); - } - - const OctreeNode &node = nodes[node_idx]; - if (std::abs(node.mass_total) <= 0.001f) { - return Vector3Zero(); - } - - Vector3 diff = Vector3Subtract(pos, node.mass_center); - float dist_sq = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z; - - // Softening - dist_sq += SOFTENING; - - float size = node.box_max.x - node.box_min.x; - - // Barnes-Hut - if (node.leaf || (size * size / dist_sq) < (THETA * THETA)) { - float dist = std::sqrt(dist_sq); - float force_mag = BH_FORCE * node.mass_total / dist_sq; - - return Vector3Scale(diff, force_mag / dist); - } - - // Collect child forces - Vector3 force = Vector3Zero(); - for (int i = 0; i < 8; ++i) { - if (node.children[i] >= 0) { - Vector3 child_force = CalculateForce(node.children[i], pos); - - force = Vector3Add(force, child_force); +auto octree::calculate_force(const int node_idx, const Vector3& pos) const -> Vector3 +{ + if (node_idx < 0) { + return Vector3Zero(); } - } - return force; -} + const node& n = nodes[node_idx]; + if (std::abs(n.mass_total) <= 0.001f) { + return Vector3Zero(); + } + + const Vector3 diff = Vector3Subtract(pos, n.mass_center); + float dist_sq = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z; + + // Softening + dist_sq += SOFTENING; + + // Barnes-Hut + const float size = n.box_max.x - n.box_min.x; + if (n.leaf || size * size / dist_sq < THETA * THETA) { + const float dist = std::sqrt(dist_sq); + const float force_mag = BH_FORCE * n.mass_total / dist_sq; + + return Vector3Scale(diff, force_mag / dist); + } + + // Collect child forces + Vector3 force = Vector3Zero(); + for (const int child : n.children) { + if (child >= 0) { + const Vector3 child_force = calculate_force(child, pos); + + force = Vector3Add(force, child_force); + } + } + + return force; +} \ No newline at end of file diff --git a/src/physics.cpp b/src/physics.cpp index 7cdc12d..d2abdff 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -4,350 +4,415 @@ #include #include #include -#include #include #include #include #include #ifdef TRACY -#include "tracy.hpp" #include #endif -auto Mass::ClearForce() -> void { force = Vector3Zero(); } - -auto Mass::CalculateVelocity(const float delta_time) -> void { - Vector3 acceleration; - Vector3 temp; - - acceleration = Vector3Scale(force, 1.0 / MASS); - temp = Vector3Scale(acceleration, delta_time); - velocity = Vector3Add(velocity, temp); +auto mass::clear_force() -> void +{ + force = Vector3Zero(); } -auto Mass::CalculatePosition(const float delta_time) -> void { - previous_position = position; - - Vector3 temp; - - temp = Vector3Scale(velocity, delta_time); - position = Vector3Add(position, temp); +auto mass::calculate_velocity(const float delta_time) -> void +{ + const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS); + const Vector3 temp = Vector3Scale(acceleration, delta_time); + velocity = Vector3Add(velocity, temp); } -auto Mass::VerletUpdate(const float delta_time) -> void { - Vector3 acceleration = Vector3Scale(force, 1.0 / MASS); - Vector3 temp_position = position; +auto mass::calculate_position(const float delta_time) -> void +{ + previous_position = position; - Vector3 displacement = Vector3Subtract(position, previous_position); - Vector3 accel_term = Vector3Scale(acceleration, delta_time * delta_time); - - // Minimal dampening - displacement = Vector3Scale(displacement, 1.0 - VERLET_DAMPENING); - - position = Vector3Add(Vector3Add(position, displacement), accel_term); - previous_position = temp_position; + const Vector3 temp = Vector3Scale(velocity, delta_time); + position = Vector3Add(position, temp); } -auto Spring::CalculateSpringForce(Mass &_mass_a, Mass &_mass_b) const -> void { - Vector3 delta_position = Vector3Subtract(_mass_a.position, _mass_b.position); - float current_length = Vector3Length(delta_position); - float inv_current_length = 1.0 / current_length; - Vector3 delta_velocity = Vector3Subtract(_mass_a.velocity, _mass_b.velocity); +auto mass::verlet_update(const float delta_time) -> void +{ + const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS); + const Vector3 temp_position = position; - float hooke = SPRING_CONSTANT * (current_length - REST_LENGTH); - float dampening = DAMPENING_CONSTANT * - Vector3DotProduct(delta_velocity, delta_position) * - inv_current_length; + Vector3 displacement = Vector3Subtract(position, previous_position); + const Vector3 accel_term = Vector3Scale(acceleration, delta_time * delta_time); - Vector3 force_a = - Vector3Scale(delta_position, -(hooke + dampening) * inv_current_length); - Vector3 force_b = Vector3Scale(force_a, -1.0); + // Minimal dampening + displacement = Vector3Scale(displacement, 1.0 - VERLET_DAMPENING); - _mass_a.force = Vector3Add(_mass_a.force, force_a); - _mass_b.force = Vector3Add(_mass_b.force, force_b); + position = Vector3Add(Vector3Add(position, displacement), accel_term); + previous_position = temp_position; } -auto MassSpringSystem::AddMass() -> void { masses.emplace_back(Vector3Zero()); } +auto spring::calculate_spring_force(mass& _a, mass& _b) -> void +{ + // TODO: Use a bungee force here instead of springs, since we already have global repulsion? + const Vector3 delta_position = Vector3Subtract(_a.position, _b.position); + const float current_length = Vector3Length(delta_position); + const float inv_current_length = 1.0f / current_length; + const Vector3 delta_velocity = Vector3Subtract(_a.velocity, _b.velocity); -auto MassSpringSystem::AddSpring(int a, int b) -> void { - Mass &mass_a = masses.at(a); - Mass &mass_b = masses.at(b); + const float hooke = SPRING_CONSTANT * (current_length - REST_LENGTH); + const float dampening = DAMPENING_CONSTANT * Vector3DotProduct(delta_velocity, delta_position) * inv_current_length; - Vector3 position = mass_a.position; - Vector3 offset = Vector3(static_cast(GetRandomValue(-100, 100)), - static_cast(GetRandomValue(-100, 100)), - static_cast(GetRandomValue(-100, 100))); - offset = Vector3Scale(Vector3Normalize(offset), REST_LENGTH); + const Vector3 force_a = Vector3Scale(delta_position, -(hooke + dampening) * inv_current_length); + const Vector3 force_b = Vector3Scale(force_a, -1.0); - if (mass_b.position == Vector3Zero()) { - mass_b.position = Vector3Add(position, offset); - } - - springs.emplace_back(a, b); + _a.force = Vector3Add(_a.force, force_a); + _b.force = Vector3Add(_b.force, force_b); } -auto MassSpringSystem::Clear() -> void { - masses.clear(); - springs.clear(); - octree.nodes.clear(); +auto mass_spring_system::add_mass() -> void +{ + // Adding all positions to (0, 0, 0) breaks the octree + + // Done when adding springs + // Vector3 position{ + // static_cast(GetRandomValue(-100, 100)), static_cast(GetRandomValue(-100, 100)), + // static_cast(GetRandomValue(-100, 100)) + // }; + // position = Vector3Scale(Vector3Normalize(position), REST_LENGTH * 2.0); + + masses.emplace_back(Vector3Zero()); } -auto MassSpringSystem::ClearForces() -> void { -#ifdef TRACY - ZoneScoped; -#endif +auto mass_spring_system::add_spring(size_t a, size_t b) -> void +{ + // Update masses to be located along a random walk when adding the springs + const mass& mass_a = masses.at(a); + mass& mass_b = masses.at(b); - for (auto &mass : masses) { - mass.ClearForce(); - } + Vector3 offset{ + static_cast(GetRandomValue(-100, 100)), static_cast(GetRandomValue(-100, 100)), + static_cast(GetRandomValue(-100, 100)) + }; + offset = Vector3Normalize(offset) * REST_LENGTH; + + // If the offset moves the mass closer to the current center of mass, flip it + if (!tree.nodes.empty()) { + const Vector3 mass_center_direction = Vector3Subtract(mass_a.position, tree.nodes.at(0).mass_center); + const float mass_center_distance = Vector3Length(mass_center_direction); + + if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) { + offset = Vector3Negate(offset); + } + } + + mass_b.position = mass_a.position + offset; + mass_b.previous_position = mass_b.position; + + // infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y, mass_a.position.z, + // mass_b.position.x, mass_b.position.y, mass_b.position.z); + + springs.emplace_back(a, b); } -auto MassSpringSystem::CalculateSpringForces() -> void { -#ifdef TRACY - ZoneScoped; -#endif +auto mass_spring_system::clear() -> void +{ + masses.clear(); + springs.clear(); + tree.nodes.clear(); +} - for (const auto spring : springs) { - Mass &a = masses.at(spring.a); - Mass &b = masses.at(spring.b); - spring.CalculateSpringForce(a, b); - } +auto mass_spring_system::clear_forces() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + for (auto& mass : masses) { + mass.clear_force(); + } +} + +auto mass_spring_system::calculate_spring_forces() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + for (const auto s : springs) { + mass& a = masses.at(s.a); + mass& b = masses.at(s.b); + spring::calculate_spring_force(a, b); + } } #ifdef THREADPOOL -auto MassSpringSystem::SetThreadName(std::size_t idx) -> void { - BS::this_thread::set_os_thread_name(std::format("bh-worker-{}", idx)); +auto mass_spring_system::set_thread_name(size_t idx) -> void +{ + BS::this_thread::set_os_thread_name(std::format("bh-worker-{}", idx)); } #endif -auto MassSpringSystem::BuildOctree() -> void { -#ifdef TRACY - ZoneScoped; -#endif +auto mass_spring_system::build_octree() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif - octree.nodes.clear(); - octree.nodes.reserve(masses.size() * 2); + tree.nodes.clear(); + tree.nodes.reserve(masses.size() * 2); - // Compute bounding box around all masses - Vector3 min = Vector3(FLT_MAX, FLT_MAX, FLT_MAX); - Vector3 max = Vector3(-FLT_MAX, -FLT_MAX, -FLT_MAX); - for (const auto &mass : masses) { - min.x = std::min(min.x, mass.position.x); - max.x = std::max(max.x, mass.position.x); - min.y = std::min(min.y, mass.position.y); - max.y = std::max(max.y, mass.position.y); - min.z = std::min(min.z, mass.position.z); - max.z = std::max(max.z, mass.position.z); - } + // Compute bounding box around all masses + Vector3 min{FLT_MAX, FLT_MAX, FLT_MAX}; + Vector3 max{-FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (const auto& mass : masses) { + min.x = std::min(min.x, mass.position.x); + max.x = std::max(max.x, mass.position.x); + min.y = std::min(min.y, mass.position.y); + max.y = std::max(max.y, mass.position.y); + min.z = std::min(min.z, mass.position.z); + max.z = std::max(max.z, mass.position.z); + } - // Pad the bounding box - float pad = 1.0; - min = Vector3Subtract(min, Vector3Scale(Vector3One(), pad)); - max = Vector3Add(max, Vector3Scale(Vector3One(), pad)); + // Pad the bounding box + constexpr float pad = 1.0; + min = Vector3Subtract(min, Vector3Scale(Vector3One(), pad)); + max = Vector3Add(max, Vector3Scale(Vector3One(), pad)); - // Make it cubic (so subdivisions are balanced) - float max_extent = std::max({max.x - min.x, max.y - min.y, max.z - min.z}); - max = Vector3Add(min, Vector3Scale(Vector3One(), max_extent)); + // Make it cubic (so subdivisions are balanced) + const float max_extent = std::max({max.x - min.x, max.y - min.y, max.z - min.z}); + max = Vector3Add(min, Vector3Scale(Vector3One(), max_extent)); - // Root node spans the entire area - int root = octree.CreateNode(min, max); + // Root node spans the entire area + const int root = tree.create_empty_leaf(min, max); - for (std::size_t i = 0; i < masses.size(); ++i) { - octree.Insert(root, i, masses[i].position, MASS); - } + for (size_t i = 0; i < masses.size(); ++i) { + tree.insert(root, i, masses[i].position, MASS, 0); + } } -auto MassSpringSystem::CalculateRepulsionForces() -> void { -#ifdef TRACY - ZoneScoped; -#endif +auto mass_spring_system::calculate_repulsion_forces() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif - BuildOctree(); + build_octree(); - auto solve_octree = [&](int i) { - Vector3 force = octree.CalculateForce(0, masses[i].position); - masses[i].force = Vector3Add(masses[i].force, force); - }; - -// Calculate forces using Barnes-Hut -#ifdef THREADPOOL - BS::multi_future loop_future = - threads.submit_loop(0, masses.size(), solve_octree, 256); - loop_future.wait(); -#else - for (std::size_t i = 0; i < masses.size(); ++i) { - solve_octree(i); - } -#endif -} - -auto MassSpringSystem::VerletUpdate(float delta_time) -> void { -#ifdef TRACY - ZoneScoped; -#endif - - for (auto &mass : masses) { - mass.VerletUpdate(delta_time); - } -} - -auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state) - -> void { -#ifdef THREADPOOL - BS::this_thread::set_os_thread_name("physics"); -#endif - - MassSpringSystem mass_springs; - - const auto visitor = overloads{ - [&](const struct AddMass &am) { mass_springs.AddMass(); }, - [&](const struct AddSpring &as) { mass_springs.AddSpring(as.a, as.b); }, - [&](const struct ClearGraph &cg) { mass_springs.Clear(); }, - }; - - std::chrono::time_point last = std::chrono::high_resolution_clock::now(); - std::chrono::duration physics_accumulator(0); - std::chrono::duration ups_accumulator(0); - unsigned int loop_iterations = 0; - - while (state.running.load()) { -#ifdef TRACY - FrameMarkStart("PhysicsThread"); -#endif - - // Time tracking - std::chrono::time_point now = std::chrono::high_resolution_clock::now(); - std::chrono::duration deltatime = now - last; - physics_accumulator += deltatime; - ups_accumulator += deltatime; - last = now; - - // Handle queued commands + auto solve_octree = [&](const int i) { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - while (!state.pending_commands.empty()) { - Command &cmd = state.pending_commands.front(); - cmd.visit(visitor); - state.pending_commands.pop(); - } + const Vector3 force = tree.calculate_force(0, masses[i].position); + masses[i].force = Vector3Add(masses[i].force, force); + }; + + // Calculate forces using Barnes-Hut + #ifdef THREADPOOL + const BS::multi_future loop_future = threads.submit_loop(0, masses.size(), solve_octree, 256); + loop_future.wait(); + #else + for (size_t i = 0; i < masses.size(); ++i) { + solve_octree(i); } + #endif +} - if (mass_springs.masses.empty()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; +auto mass_spring_system::verlet_update(const float delta_time) -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + for (auto& mass : masses) { + mass.verlet_update(delta_time); } +} - // Physics update - if (physics_accumulator.count() > TIMESTEP) { - mass_springs.ClearForces(); - mass_springs.CalculateSpringForces(); - mass_springs.CalculateRepulsionForces(); - mass_springs.VerletUpdate(TIMESTEP * SIM_SPEED); - - ++loop_iterations; - physics_accumulator -= std::chrono::duration(TIMESTEP); +auto mass_spring_system::center_masses() -> void +{ + Vector3 mean = Vector3Zero(); + for (const auto& mass : masses) { + mean += mass.position; } + mean /= masses.size(); - // Publish the positions for the renderer (copy) -#ifdef TRACY - FrameMarkStart("PhysicsThreadProduceLock"); -#endif + for (auto& mass : masses) { + mass.position -= mean; + } +} + +auto threaded_physics::physics_thread(physics_state& state) -> void +{ + #ifdef THREADPOOL + BS::this_thread::set_os_thread_name("physics"); + #endif + + mass_spring_system mass_springs; + + const auto visitor = overloads{ + [&](const struct add_mass& am) + { + mass_springs.add_mass(); + }, + [&](const struct add_spring& as) + { + mass_springs.add_spring(as.a, as.b); + }, + [&](const struct clear_graph& cg) + { + mass_springs.clear(); + }, + }; + + std::chrono::time_point last = std::chrono::high_resolution_clock::now(); + std::chrono::duration physics_accumulator(0); + std::chrono::duration ups_accumulator(0); + int loop_iterations = 0; + + while (state.running.load()) { + #ifdef TRACY + FrameMarkStart("PhysicsThread"); + #endif + + // Time tracking + std::chrono::time_point now = std::chrono::high_resolution_clock::now(); + const std::chrono::duration deltatime = now - last; + physics_accumulator += deltatime; + ups_accumulator += deltatime; + last = now; + + // Handle queued commands + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + while (!state.pending_commands.empty()) { + command& cmd = state.pending_commands.front(); + cmd.visit(visitor); + state.pending_commands.pop(); + } + } + + if (mass_springs.masses.empty()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + + // Physics update + if (physics_accumulator.count() > TIMESTEP) { + mass_springs.clear_forces(); + mass_springs.calculate_spring_forces(); + mass_springs.calculate_repulsion_forces(); + mass_springs.verlet_update(TIMESTEP * SIM_SPEED); + + // This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just expensive + // and yields no benefit since we can lock the camera to the center of mass cheaply. + // mass_springs.center_masses(); + + ++loop_iterations; + physics_accumulator -= std::chrono::duration(TIMESTEP); + } + + // Publish the positions for the renderer (copy) + #ifdef TRACY + FrameMarkStart("PhysicsThreadProduceLock"); + #endif + { + #ifdef TRACY + std::unique_lock lock(state.data_mtx); + #else + std::unique_lock lock(state.data_mtx); + #endif + state.data_consumed_cnd.wait(lock, [&] + { + return state.data_consumed || !state.running.load(); + }); + if (!state.running.load()) { + // Running turned false while we were waiting for the condition + break; + } + + if (ups_accumulator.count() > 1.0) { + // Update each second + state.ups = loop_iterations; + loop_iterations = 0; + ups_accumulator = std::chrono::duration(0); + } + if (mass_springs.tree.nodes.empty()) { + state.mass_center = Vector3Zero(); + } else { + state.mass_center = mass_springs.tree.nodes.at(0).mass_center; + } + + state.masses.clear(); + state.masses.reserve(mass_springs.masses.size()); + for (const auto& mass : mass_springs.masses) { + state.masses.emplace_back(mass.position); + } + + state.mass_count = mass_springs.masses.size(); + state.spring_count = mass_springs.springs.size(); + + state.data_ready = true; + state.data_consumed = false; + } + // Notify the rendering thread that new data is available + state.data_ready_cnd.notify_all(); + #ifdef TRACY + FrameMarkEnd("PhysicsThreadProduceLock"); + + FrameMarkEnd("PhysicsThread"); + #endif + } +} + +auto threaded_physics::add_mass_cmd() -> void +{ { -#ifdef TRACY - std::unique_lock lock(state.data_mtx); -#else - std::unique_lock lock(state.data_mtx); -#endif - state.data_consumed_cnd.wait( - lock, [&] { return state.data_consumed || !state.running.load(); }); - if (!state.running.load()) { - // Running turned false while we were waiting for the condition - break; - } - - if (ups_accumulator.count() > 1.0) { - // Update each second - state.ups = loop_iterations; - loop_iterations = 0; - ups_accumulator = std::chrono::duration(0); - } - if (mass_springs.octree.nodes.size() > 0) { - state.mass_center = mass_springs.octree.nodes.at(0).mass_center; - } else { - state.mass_center = Vector3Zero(); - } - - state.masses.clear(); - state.masses.reserve(mass_springs.masses.size()); - for (const auto &mass : mass_springs.masses) { - state.masses.emplace_back(mass.position); - } - - state.data_ready = true; - state.data_consumed = false; + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + state.pending_commands.emplace(add_mass{}); } - // Notify the rendering thread that new data is available - state.data_ready_cnd.notify_all(); -#ifdef TRACY - FrameMarkEnd("PhysicsThreadProduceLock"); - - FrameMarkEnd("PhysicsThread"); -#endif - } } -auto ThreadedPhysics::AddMassCmd() -> void { - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - state.pending_commands.push(AddMass{}); - } -} - -auto ThreadedPhysics::AddSpringCmd(std::size_t a, std::size_t b) -> void { - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - state.pending_commands.push(AddSpring{a, b}); - } -} - -auto ThreadedPhysics::ClearCmd() -> void { - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - state.pending_commands.push(ClearGraph{}); - } -} - -auto ThreadedPhysics::AddMassSpringsCmd( - std::size_t num_masses, - const std::vector> &springs) -> void { - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - for (std::size_t i = 0; i < num_masses; ++i) { - state.pending_commands.push(AddMass{}); +auto threaded_physics::add_spring_cmd(const size_t a, const size_t b) -> void +{ + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + state.pending_commands.emplace(add_spring{a, b}); } - for (const auto &[from, to] : springs) { - state.pending_commands.push(AddSpring{from, to}); - } - } } + +auto threaded_physics::clear_cmd() -> void +{ + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + state.pending_commands.emplace(clear_graph{}); + } +} + +auto threaded_physics::add_mass_springs_cmd(const size_t num_masses, + const std::vector>& springs) -> void +{ + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + for (size_t i = 0; i < num_masses; ++i) { + state.pending_commands.emplace(add_mass{}); + } + for (const auto& [from, to] : springs) { + state.pending_commands.emplace(add_spring{from, to}); + } + } +} \ No newline at end of file diff --git a/src/puzzle.cpp b/src/puzzle.cpp index 415ea45..bc656eb 100644 --- a/src/puzzle.cpp +++ b/src/puzzle.cpp @@ -1,396 +1,509 @@ #include "puzzle.hpp" -#include "config.hpp" #include #ifdef TRACY -#include "tracy.hpp" #include #endif -auto Block::Hash() const -> std::size_t { - std::string s = std::format("{},{},{},{}", x, y, width, height); - return std::hash{}(s); +auto puzzle::block::hash() const -> size_t +{ + const std::string s = std::format("{},{},{},{}", x, y, width, height); + return std::hash{}(s); } -auto Block::Invalid() -> Block { - Block block = Block(0, 0, 1, 1, false, false); - block.width = 0; - block.height = 0; - return block; +auto puzzle::block::valid() const -> bool +{ + return width > 0 && height > 0 && x >= 0 && x + width <= 9 && y >= 0 && y + height <= 9; } -auto Block::IsValid() const -> bool { return width != 0 && height != 0; } - -auto Block::ToString() const -> std::string { - if (target) { - return std::format("{}{}", - static_cast(width + static_cast('a') - 1), - static_cast(height + static_cast('a') - 1)); - } else if (immovable) { - return std::format("{}{}", - static_cast(width + static_cast('A') - 1), - static_cast(height + static_cast('A') - 1)); - } else { +auto puzzle::block::string() const -> std::string +{ + if (target) { + return std::format("{}{}", static_cast(width + static_cast('a') - 1), + static_cast(height + static_cast('a') - 1)); + } + if (immovable) { + return std::format("{}{}", static_cast(width + static_cast('A') - 1), + static_cast(height + static_cast('A') - 1)); + } return std::format("{}{}", width, height); - } } -auto Block::GetPrincipalDirs() const -> int { - if (immovable) { - return 0; - } - - if (width > height) { - return Direction::EAS | Direction::WES; - } else if (height > width) { - return Direction::NOR | Direction::SOU; - } else { - return Direction::NOR | Direction::EAS | Direction::SOU | Direction::WES; - } -} - -auto Block::Covers(int xx, int yy) const -> bool { - return xx >= x && xx < x + width && yy >= y && yy < y + height; -} - -auto Block::Collides(const Block &other) const -> bool { - return x < other.x + other.width && x + width > other.x && - y < other.y + other.height && y + height > other.y; -} - -auto State::Hash() const -> std::size_t { - return std::hash{}(state); -} - -auto State::HasWinCondition() const -> bool { - return target_x != 9 && target_y != 9; -} - -auto State::IsWon() const -> bool { - if (!HasWinCondition()) { - return false; - } - - for (const auto &block : *this) { - if (block.target) { - return block.x == target_x && block.y == target_y; - } - } - - return false; -} - -auto State::SetGoal(int x, int y) -> bool { - Block target_block = GetTargetBlock(); - if (!target_block.IsValid() || x < 0 || x + target_block.width > width || - y < 0 || y + target_block.height > height) { - return false; - } - - if (target_x == x && target_y == y) { - target_x = 9; - target_y = 9; - } else { - target_x = x; - target_y = y; - } - - state.replace(3, 1, std::format("{}", target_x)); - state.replace(4, 1, std::format("{}", target_y)); - - return true; -} - -auto State::ClearGoal() -> void { - target_x = 9; - target_y = 9; - state.replace(3, 2, "99"); -} - -auto State::AddColumn() const -> State { - State newstate = State(width + 1, height, restricted); - - for (const auto &block : *this) { - newstate.AddBlock(block); - } - - return newstate; -} - -auto State::RemoveColumn() const -> State { - State newstate = State(width - 1, height, restricted); - - for (const auto &block : *this) { - newstate.AddBlock(block); - } - - return newstate; -} - -auto State::AddRow() const -> State { - State newstate = State(width, height + 1, restricted); - - for (const auto &block : *this) { - newstate.AddBlock(block); - } - - return newstate; -} - -auto State::RemoveRow() const -> State { - State newstate = State(width, height - 1, restricted); - - for (const auto &block : *this) { - newstate.AddBlock(block); - } - - return newstate; -} - -auto State::AddBlock(const Block &block) -> bool { - if (block.x + block.width > width || block.y + block.height > height) { - return false; - } - - for (Block b : *this) { - if (b.Collides(block)) { - return false; - } - } - - int index = GetIndex(block.x, block.y); - state.replace(index, 2, block.ToString()); - - return true; -} - -auto State::GetBlock(int x, int y) const -> Block { - if (x >= width || y >= height) { - return Block::Invalid(); - } - - for (Block b : *this) { - if (b.Covers(x, y)) { - return b; - } - } - - return Block::Invalid(); -} - -auto State::GetBlockAt(int x, int y) const -> std::string { - return state.substr(GetIndex(x, y), 2); -} - -auto State::GetTargetBlock() const -> Block { - for (Block b : *this) { - if (b.target) { - return b; - } - } - - return Block::Invalid(); -} - -auto State::GetIndex(int x, int y) const -> int { - return prefix + (y * width + x) * 2; -} - -auto State::RemoveBlock(int x, int y) -> bool { - Block block = GetBlock(x, y); - if (!block.IsValid()) { - return false; - } - - int index = GetIndex(block.x, block.y); - state.replace(index, 2, ".."); - - return true; -} - -auto State::ToggleTarget(int x, int y) -> bool { - Block block = GetBlock(x, y); - if (!block.IsValid() || block.immovable) { - return false; - } - - // Remove the current target - int index; - for (const auto &b : *this) { - if (b.target) { - index = GetIndex(b.x, b.y); - state.replace(index, 2, - Block(b.x, b.y, b.width, b.height, false).ToString()); - break; - } - } - - // Add the new target - block.target = !block.target; - index = GetIndex(block.x, block.y); - state.replace(index, 2, block.ToString()); - - return true; -} - -auto State::ToggleWall(int x, int y) -> bool { - Block block = GetBlock(x, y); - if (!block.IsValid() || block.target) { - return false; - } - - // Add the new target - block.immovable = !block.immovable; - int index = GetIndex(block.x, block.y); - state.replace(index, 2, block.ToString()); - - return true; -} - -auto State::ToggleRestricted() -> void { - restricted = !restricted; - state.replace(0, 1, restricted ? "R" : "F"); -} - -auto State::MoveBlockAt(int x, int y, Direction dir) -> bool { - Block block = GetBlock(x, y); - if (!block.IsValid() || block.immovable) { - return false; - } - - int dirs = restricted ? block.GetPrincipalDirs() - : Direction::NOR | Direction::EAS | Direction::SOU | - Direction::WES; - - // Get target block - int _target_x = block.x; - int _target_y = block.y; - switch (dir) { - case Direction::NOR: - if (!(dirs & Direction::NOR) || _target_y < 1) { - return false; - } - _target_y--; - break; - case Direction::EAS: - if (!(dirs & Direction::EAS) || _target_x + block.width >= width) { - return false; - } - _target_x++; - break; - case Direction::SOU: - if (!(dirs & Direction::SOU) || _target_y + block.height >= height) { - return false; - } - _target_y++; - break; - case Direction::WES: - if (!(dirs & Direction::WES) || _target_x < 1) { - return false; - } - _target_x--; - break; - } - Block target = - Block(_target_x, _target_y, block.width, block.height, block.target); - - // Check collisions - for (Block b : *this) { - if (b != block && b.Collides(target)) { - return false; - } - } - - RemoveBlock(x, y); - AddBlock(target); - - return true; -} - -auto State::GetNextStates() const -> std::vector { - std::vector new_states; - - for (const Block &b : *this) { - int dirs = restricted ? b.GetPrincipalDirs() - : Direction::NOR | Direction::EAS | Direction::SOU | - Direction::WES; - - if (b.immovable) { - continue; +auto puzzle::block::principal_dirs() const -> int +{ + if (immovable) { + return 0; } - if (dirs & Direction::NOR) { - State north = *this; - if (north.MoveBlockAt(b.x, b.y, Direction::NOR)) { - new_states.push_back(north); - } + if (width > height) { + return eas | wes; } - - if (dirs & Direction::EAS) { - State east = *this; - if (east.MoveBlockAt(b.x, b.y, Direction::EAS)) { - new_states.push_back(east); - } + if (height > width) { + return nor | sou; } - - if (dirs & Direction::SOU) { - State south = *this; - if (south.MoveBlockAt(b.x, b.y, Direction::SOU)) { - new_states.push_back(south); - } - } - - if (dirs & Direction::WES) { - State west = *this; - if (west.MoveBlockAt(b.x, b.y, Direction::WES)) { - new_states.push_back(west); - } - } - } - - return new_states; + return nor | eas | sou | wes; } -auto State::Closure() const - -> std::pair, - std::vector>> { -#ifdef TRACY - ZoneScoped; -#endif - - std::vector states; - std::vector> links; - - // Helper to construct the links vector - std::unordered_map state_indices; - - // Buffer for all states we want to call GetNextStates() on - std::unordered_set remaining_states; - remaining_states.insert(*this); - - do { - const State current = *remaining_states.begin(); - remaining_states.erase(current); - - if (!state_indices.contains(current)) { - state_indices.emplace(current, states.size()); - states.push_back(current); - } - - for (const State &s : current.GetNextStates()) { - if (!state_indices.contains(s)) { - remaining_states.insert(s); - state_indices.emplace(s, states.size()); - states.push_back(s); - } - links.emplace_back(state_indices.at(current), state_indices.at(s)); - } - } while (remaining_states.size() > 0); - - std::cout << std::format("State space has size {} with {} transitions.", - states.size(), links.size()) - << std::endl; - - return std::make_pair(states, links); +auto puzzle::block::covers(const int _x, const int _y) const -> bool +{ + return _x >= x && _x < x + width && _y >= y && _y < y + height; } + +auto puzzle::block::collides(const block& b) const -> bool +{ + return x < b.x + b.width && x + width > b.x && y < b.y + b.height && y + height > b.y; +} + +auto puzzle::get_index(const int x, const int y) const -> int +{ + if (x < 0 || x >= width || y < 0 || y >= height) { + errln("Trying to calculating index of invalid board coordinates ({}, {})", x, y); + exit(1); + } + return PREFIX + (y * width + x) * 2; +} + +auto puzzle::hash() const -> size_t +{ + return std::hash{}(state); +} + +auto puzzle::has_win_condition() const -> bool +{ + return target_x != MAX_WIDTH && target_y != MAX_HEIGHT; +} + +auto puzzle::won() const -> bool +{ + const std::optional& b = try_get_target_block(); + return has_win_condition() && b && b->x == target_x && b->y == target_y; +} + +auto puzzle::valid() const -> bool +{ + return width >= MIN_WIDTH && width <= MAX_WIDTH && height >= MIN_HEIGHT && height <= MAX_HEIGHT; +} + +auto puzzle::valid_thorough() const -> bool +{ + if (has_win_condition() && !try_get_target_block()) { + return false; + } + + infoln("Validating puzzle {}", state); + + if (static_cast(state.length()) != width * height * 2 + PREFIX) { + infoln("Puzzle invalid: Representation length {} doesn't match {}x{} board", state.length(), width, height); + return false; + } + + // Check prefix + if (!std::string("FR").contains(state[0])) { + infoln("Puzzle invalid: Representation[0] {} doesn't match [FR]", state[0]); + return false; + } + if (restricted && state[0] != 'R') { + infoln("Puzzle invalid: Representation[0] {} doesn't match restricted={}", state[0], restricted); + return false; + } + if (!std::string("3456789").contains(state[1]) || !std::string("3456789").contains(state[2])) { + infoln("Puzzle invalid: Representation[1/2] {}/{} doesn't match [3-9]", state[1], state[2]); + return false; + } + if (std::stoi(state.substr(1, 1)) != width || std::stoi(state.substr(2, 1)) != height) { + infoln("Puzzle invalid: Representation[1/2] {}/{} doesn't match {}x{} board", state[1], state[2], width, + height); + return false; + } + if (!std::string("012345678").contains(state[3]) || !std::string("012345678").contains(state[4])) { + infoln("Puzzle invalid: Representation[3/4] {}/{} doesn't match [1-9]", state[1], state[2]); + return false; + } + if (std::stoi(state.substr(3, 1)) != target_x || std::stoi(state.substr(4, 1)) != target_y) { + infoln("Puzzle invalid: Representation[3/4] {}/{} doesn't match target ({}, {})", state[1], state[2], target_x, + target_y); + return false; + } + + // Check blocks + const std::string allowed_chars = ".123456789abcdefghiABCDEFGHI"; + for (const char c : state.substr(PREFIX, state.length() - PREFIX)) { + if (!allowed_chars.contains(c)) { + infoln("Puzzle invalid: Block {} has invalid character", c); + return false; + } + } + + const bool success = width >= MIN_WIDTH && width <= MAX_WIDTH && height >= MIN_HEIGHT && height <= MAX_HEIGHT; + + if (!success) { + infoln("Puzzle invalid: Board size {}x{} not in range [3-9]", width, height); + } + + return success; +} + +auto puzzle::try_get_block(const int x, const int y) const -> std::optional +{ + if (!covers(x, y)) { + return std::nullopt; + } + + for (const block& b : *this) { + if (b.covers(x, y)) { + return b; + } + } + + return std::nullopt; +} + +auto puzzle::try_get_target_block() const -> std::optional +{ + for (const block b : *this) { + if (b.target) { + return b; + } + } + + return std::nullopt; +} + +auto puzzle::covers(const int x, const int y, const int w, const int h) const -> bool +{ + return x >= 0 && x + w <= width && y >= 0 && y + h <= height; +} + +auto puzzle::covers(const int x, const int y) const -> bool +{ + return covers(x, y, 1, 1); +} + +auto puzzle::covers(const block& b) const -> bool +{ + return covers(b.x, b.y, b.width, b.height); +} + +auto puzzle::try_toggle_restricted() const -> std::optional +{ + puzzle p = *this; + p.restricted = !restricted; + p.state.replace(0, 1, p.restricted ? "R" : "F"); + return p; +} + +auto puzzle::try_set_goal(const int x, const int y) const -> std::optional +{ + const std::optional& b = try_get_target_block(); + if (!b || !covers(x, y, b->width, b->height)) { + return std::nullopt; + } + + puzzle p = *this; + if (target_x == x && target_y == y) { + p.target_x = MAX_WIDTH; + p.target_y = MAX_HEIGHT; + } else { + p.target_x = x; + p.target_y = y; + } + + p.state.replace(3, 1, std::format("{}", target_x)); + p.state.replace(4, 1, std::format("{}", target_y)); + + return p; +} + +auto puzzle::try_clear_goal() const -> std::optional +{ + puzzle p = *this; + p.target_x = MAX_WIDTH; + p.target_y = MAX_HEIGHT; + p.state.replace(3, 2, "99"); + return p; +} + +auto puzzle::try_add_column() const -> std::optional +{ + if (width >= MAX_WIDTH) { + return std::nullopt; + } + + puzzle p = {width + 1, height, restricted}; + + // Non-fitting blocks won't be added + for (const block& b : *this) { + if (const std::optional& _p = p.try_add_block(b)) { + p = *_p; + } + } + + return p; +} + +auto puzzle::try_remove_column() const -> std::optional +{ + if (width <= MIN_WIDTH) { + return std::nullopt; + } + + puzzle p = {width - 1, height, restricted}; + + // Non-fitting blocks won't be added + for (const block& b : *this) { + if (const std::optional& _p = p.try_add_block(b)) { + p = *_p; + } + } + + return p; +} + +auto puzzle::try_add_row() const -> std::optional +{ + if (height >= 9) { + return std::nullopt; + } + + puzzle p = puzzle(width, height + 1, restricted); + + for (const block& b : *this) { + if (const std::optional& _p = p.try_add_block(b)) { + p = *_p; + } + } + + return p; +} + +auto puzzle::try_remove_row() const -> std::optional +{ + if (height == 0) { + return std::nullopt; + } + + puzzle p = puzzle(width, height - 1, restricted); + + for (const block& b : *this) { + if (const std::optional& _p = p.try_add_block(b)) { + p = *_p; + } + } + + return p; +} + +auto puzzle::try_add_block(const block& b) const -> std::optional +{ + if (!covers(b)) { + return std::nullopt; + } + + for (block _b : *this) { + if (_b.collides(b)) { + return std::nullopt; + } + } + + puzzle p = *this; + const int index = get_index(b.x, b.y); + p.state.replace(index, 2, b.string()); + + return p; +} + +auto puzzle::try_remove_block(const int x, const int y) const -> std::optional +{ + const std::optional& b = try_get_block(x, y); + if (!b) { + return std::nullopt; + } + + puzzle p = *this; + const int index = get_index(b->x, b->y); + p.state.replace(index, 2, ".."); + + return p; +} + +auto puzzle::try_toggle_target(const int x, const int y) const -> std::optional +{ + std::optional b = try_get_block(x, y); + if (!b || b->immovable) { + return std::nullopt; + } + + // Remove the current target if it exists + puzzle p = *this; + if (const std::optional& _b = try_get_target_block()) { + const int index = get_index(_b->x, _b->y); + p.state.replace(index, 2, block(_b->x, _b->y, _b->width, _b->height, false).string()); + } + + // Add the new target + b->target = !b->target; + const int index = get_index(b->x, b->y); + p.state.replace(index, 2, b->string()); + + return p; +} + +auto puzzle::try_toggle_wall(const int x, const int y) const -> std::optional +{ + std::optional b = try_get_block(x, y); + if (!b || b->target) { + return std::nullopt; + } + + // Add the new target + puzzle p = *this; + b->immovable = !b->immovable; + const int index = get_index(b->x, b->y); + p.state.replace(index, 2, b->string()); + + return p; +} + +auto puzzle::try_move_block_at(const int x, const int y, const direction dir) const -> std::optional +{ + const std::optional &b = try_get_block(x, y); + if (!b || b->immovable) { + return std::nullopt; + } + + const int dirs = restricted ? b->principal_dirs() : nor | eas | sou | wes; + + // Get target block + int _target_x = b->x; + int _target_y = b->y; + switch (dir) { + case nor: + if (!(dirs & nor) || _target_y < 1) { + return std::nullopt; + } + _target_y--; + break; + case eas: + if (!(dirs & eas) || _target_x + b->width >= width) { + return std::nullopt; + } + _target_x++; + break; + case sou: + if (!(dirs & sou) || _target_y + b->height >= height) { + return std::nullopt; + } + _target_y++; + break; + case wes: + if (!(dirs & wes) || _target_x < 1) { + return std::nullopt; + } + _target_x--; + break; + } + const block moved_b = block(_target_x, _target_y, b->width, b->height, b->target); + + // Check collisions + for (const block& _b : *this) { + if (_b != b && _b.collides(moved_b)) { + return std::nullopt; + } + } + + std::optional p = try_remove_block(x, y); + if (!p) { + return std::nullopt; + } + + p = p->try_add_block(moved_b); + if (!p) { + return std::nullopt; + } + + return p; +} + +auto puzzle::find_adjacent_puzzles() const -> std::vector +{ + std::vector puzzles; + + for (const block& b : *this) { + if (b.immovable) { + continue; + } + + const int dirs = restricted ? b.principal_dirs() : nor | eas | sou | wes; + + if (dirs & nor) { + if (const std::optional &north = try_move_block_at(b.x, b.y, nor)) { + puzzles.push_back(*north); + } + } + + if (dirs & eas) { + if (const std::optional &east = try_move_block_at(b.x, b.y, eas)) { + puzzles.push_back(*east); + } + } + + if (dirs & sou) { + if (const std::optional &south = try_move_block_at(b.x, b.y, sou)) { + puzzles.push_back(*south); + } + } + + if (dirs & wes) { + if (const std::optional &west = try_move_block_at(b.x, b.y, wes)) { + puzzles.push_back(*west); + } + } + } + + // for (const puzzle& p : puzzles) { + // println("Adjacent puzzle: {}", p.state); + // } + + return puzzles; +} + +auto puzzle::explore_state_space() const -> std::pair, std::vector>> +{ + #ifdef TRACY + ZoneScoped; + #endif + + infoln("Exploring state space, this might take a while..."); + + std::vector state_pool; + std::unordered_map state_indices; // Helper to construct the links vector + std::vector> links; + + // Buffer for all states we want to call GetNextStates() on + std::unordered_set remaining_states; + remaining_states.insert(*this); + + do { + const puzzle current = *remaining_states.begin(); + remaining_states.erase(current); + + if (!state_indices.contains(current)) { + state_indices.emplace(current, state_pool.size()); + state_pool.push_back(current); + } + + for (const puzzle& s : current.find_adjacent_puzzles()) { + if (!state_indices.contains(s)) { + remaining_states.insert(s); + state_indices.emplace(s, state_pool.size()); + state_pool.push_back(s); + } + links.emplace_back(state_indices.at(current), state_indices.at(s)); + } + } while (!remaining_states.empty()); + + infoln("State space has size {} with {} transitions.", state_pool.size(), links.size()); + + return std::make_pair(state_pool, links); +} \ No newline at end of file diff --git a/src/renderer.cpp b/src/renderer.cpp index 1a9f33a..e464b63 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,244 +1,224 @@ #include "renderer.hpp" #include "config.hpp" -#include "puzzle.hpp" -#include #include #include #include #ifdef TRACY -#include "tracy.hpp" #include #endif -#ifdef BATCHING -#include -#endif - -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::AllocateGraphInstancing(std::size_t size) -> void { - cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE); - - instancing_shader = LoadShader("shader/instancing_vertex.glsl", - "shader/instancing_fragment.glsl"); - instancing_shader.locs[SHADER_LOC_MATRIX_MVP] = - GetShaderLocation(instancing_shader, "mvp"); - instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] = - GetShaderLocation(instancing_shader, "viewPos"); - - vertex_mat = LoadMaterialDefault(); - vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR; - vertex_mat.shader = instancing_shader; - - transforms = (Matrix *)MemAlloc(size * sizeof(Matrix)); - transforms_size = size; -} - -auto Renderer::ReallocateGraphInstancingIfNecessary(std::size_t size) -> void { - if (transforms_size != size) { - transforms = (Matrix *)MemRealloc(transforms, size * sizeof(Matrix)); - transforms_size = size; - } -} - -auto Renderer::DrawMassSprings(const std::vector &masses) -> void { -#ifdef TRACY - ZoneScoped; -#endif - - // Prepare cube instancing - { -#ifdef TRACY - ZoneNamedN(prepare_masses, "PrepareMasses", true); -#endif - if (masses.size() < DRAW_VERTICES_LIMIT) { - if (transforms == nullptr) { - AllocateGraphInstancing(masses.size()); - } - ReallocateGraphInstancingIfNecessary(masses.size()); - - int i = 0; - for (const Vector3 &mass : masses) { - transforms[i] = MatrixTranslate(mass.x, mass.y, mass.z); - ++i; - } +auto renderer::update_texture_sizes() -> void +{ + if (!IsWindowResized()) { + return; } - } - BeginTextureMode(render_target); - ClearBackground(RAYWHITE); + UnloadRenderTexture(render_target); + UnloadRenderTexture(klotski_target); + UnloadRenderTexture(menu_target); - BeginMode3D(camera.camera); + const int width = GetScreenWidth() / 2; + const int height = GetScreenHeight() - MENU_HEIGHT; - // Draw springs (batched) - { -#ifdef TRACY - ZoneNamedN(draw_springs, "DrawSprings", true); -#endif + render_target = LoadRenderTexture(width, height); + klotski_target = LoadRenderTexture(width, height); + menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT); +} + +auto renderer::draw_mass_springs(const std::vector& masses) -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + if (masses.size() != state.get_state_count()) { + // Because the physics run in a different thread, it might need time to catch up + return; + } + + // Prepare connection batching + { + #ifdef TRACY + ZoneNamedN(prepare_masses, "PrepareConnectionsBatching", true); + #endif + + connections.clear(); + connections.reserve(state.get_target_count()); + if (input.connect_solutions) { + for (const size_t& _state : state.get_winning_indices()) { + const Vector3& current_mass = masses.at(state.get_current_index()); + const Vector3& winning_mass = masses.at(_state); + connections.emplace_back(current_mass, winning_mass); + DrawLine3D(current_mass, winning_mass, Fade(TARGET_BLOCK_COLOR, 0.5)); + } + } + } + + // Prepare cube instancing + { + #ifdef TRACY + ZoneNamedN(prepare_masses, "PrepareMassInstancing", true); + #endif + + if (masses.size() < DRAW_VERTICES_LIMIT) { + // Don't have to reserve, capacity is already set to DRAW_VERTICES_LIMIT in constructor + transforms.clear(); + colors.clear(); + + size_t mass = 0; + for (const auto& [x, y, z] : masses) { + transforms.emplace_back(MatrixTranslate(x, y, z)); + + // Normal vertex + Color c = VERTEX_COLOR; + if ((input.mark_solutions || input.mark_path) && state.get_winning_indices().contains(mass)) { + // Winning vertex + c = VERTEX_TARGET_COLOR; + } else if ((input.mark_solutions || input.mark_path) && state.get_path_indices().contains(mass)) { + // Path vertex + c = VERTEX_PATH_COLOR; + } else if (mass == state.get_starting_index()) { + // Starting vertex + c = VERTEX_START_COLOR; + } else if (state.get_visit_counts().at(mass) > 0) { + // Visited vertex + c = VERTEX_VISITED_COLOR; + } + // Current vertex is drawn as individual cube to increase its size + + colors.emplace_back(c); + ++mass; + } + } + + rlUpdateVertexBuffer(color_vbo_id, colors.data(), colors.size() * sizeof(Color), 0); + } + + BeginTextureMode(render_target); + ClearBackground(RAYWHITE); + BeginMode3D(camera.camera); + + // Draw springs (batched) + { + #ifdef TRACY + ZoneNamedN(draw_springs, "DrawSprings", true); + #endif + + rlBegin(RL_LINES); + for (const auto& [from, to] : state.get_links()) { + if (masses.size() > from && masses.size() > to) { + const auto& [ax, ay, az] = masses.at(from); + const auto& [bx, by, bz] = masses.at(to); + rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a); + rlVertex3f(ax, ay, az); + rlVertex3f(bx, by, bz); + } + } + rlEnd(); + } + + // Draw masses (instanced) + { + #ifdef TRACY + ZoneNamedN(draw_masses, "DrawMasses", true); + #endif + + if (masses.size() < DRAW_VERTICES_LIMIT) { + // NOTE: I don't know if drawing all this inside a shader would make it + // much faster... The amount of data sent to the GPU would be + // reduced (just positions instead of matrices), but is this + // noticable for < 100000 cubes? + DrawMeshInstanced(cube_instance, vertex_mat, transforms.data(), masses.size()); // NOLINT(*-narrowing-conversions) + } + } + + // Connect current to winning states (batched) + const auto [r, g, b, a] = Fade(VERTEX_CURRENT_COLOR, 0.3); rlBegin(RL_LINES); - for (const auto &[from, to] : state.springs) { - if (masses.size() > from && masses.size() > to) { - const Vector3 &a = masses.at(from); - const Vector3 &b = masses.at(to); - rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a); - rlVertex3f(a.x, a.y, a.z); - rlVertex3f(b.x, b.y, b.z); - } + for (const auto& [from, to] : connections) { + const auto& [ax, ay, az] = from; + const auto& [bx, by, bz] = to; + rlColor4ub(r, g, b, a); + rlVertex3f(ax, ay, az); + rlVertex3f(bx, by, bz); } rlEnd(); - } - // Draw masses (instanced) - { -#ifdef TRACY - ZoneNamedN(draw_masses, "DrawMasses", true); -#endif - if (masses.size() < DRAW_VERTICES_LIMIT) { - // NOTE: I don't know if drawing all this inside a shader would make it - // much faster... The amount of data sent to the GPU would be - // reduced (just positions instead of matrices), but is this - // noticable for < 100000 cubes? - DrawMeshInstanced(cube_instance, vertex_mat, transforms, masses.size()); + // Mark current state + const size_t current_index = state.get_current_index(); + if (masses.size() > current_index) { + const Vector3& current_mass = masses.at(current_index); + DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_CURRENT_COLOR); } - } - // Mark winning states - if (input.mark_solutions || input.connect_solutions) { - for (const State &_state : state.winning_states) { - - std::size_t winning_index = state.states.at(_state); - if (masses.size() > winning_index) { - - const Vector3 &winning_mass = masses.at(winning_index); - if (input.mark_solutions) { - DrawCube(winning_mass, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE, - 2 * VERTEX_SIZE, VERTEX_TARGET_COLOR); - } - - std::size_t current_index = state.CurrentMassIndex(); - if (input.connect_solutions && masses.size() > current_index) { - const Vector3 ¤t_mass = masses.at(current_index); - DrawLine3D(winning_mass, current_mass, Fade(TARGET_BLOCK_COLOR, 0.5)); - } - } - } - } - - // Mark visited states - for (const auto &[_state, visits] : state.visited_states) { - std::size_t visited_index = state.states.at(_state); - - if (masses.size() > visited_index) { - const Vector3 &visited_mass = masses.at(visited_index); - DrawCube(visited_mass, VERTEX_SIZE * 1.5, VERTEX_SIZE * 1.5, - VERTEX_SIZE * 1.5, VERTEX_VISITED_COLOR); - } - } - - // Mark winning path - if (input.mark_path) { - for (const std::size_t &_state : state.winning_path) { - if (masses.size() > _state) { - const Vector3 &path_mass = masses.at(_state); - DrawCube(path_mass, VERTEX_SIZE * 1.75, VERTEX_SIZE * 1.75, - VERTEX_SIZE * 1.75, VERTEX_PATH_COLOR); - } - } - } - - // Mark starting state - std::size_t starting_index = state.states.at(state.starting_state); - if (masses.size() > starting_index) { - const Vector3 &starting_mass = masses.at(starting_index); - DrawCube(starting_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, - VERTEX_START_COLOR); - } - - // Mark current state - std::size_t current_index = state.states.at(state.current_state); - if (masses.size() > current_index) { - const Vector3 ¤t_mass = masses.at(current_index); - DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, - VERTEX_CURRENT_COLOR); - } - - EndMode3D(); - EndTextureMode(); + EndMode3D(); + EndTextureMode(); } -auto Renderer::DrawKlotski() -> void { -#ifdef TRACY - ZoneScoped; -#endif +auto renderer::draw_klotski() const -> void +{ + #ifdef TRACY + ZoneScoped; + #endif - BeginTextureMode(klotski_target); - ClearBackground(RAYWHITE); + BeginTextureMode(klotski_target); + ClearBackground(RAYWHITE); - gui.DrawPuzzleBoard(); + gui.draw_puzzle_board(); - EndTextureMode(); + EndTextureMode(); } -auto Renderer::DrawMenu(const std::vector &masses) -> void { -#ifdef TRACY - ZoneScoped; -#endif +auto renderer::draw_menu() const -> void +{ + #ifdef TRACY + ZoneScoped; + #endif - BeginTextureMode(menu_target); - ClearBackground(RAYWHITE); + BeginTextureMode(menu_target); + ClearBackground(RAYWHITE); - gui.DrawMainMenu(); + gui.draw_main_menu(); - EndTextureMode(); + EndTextureMode(); } -auto Renderer::DrawTextures(int fps, int ups) -> void { - BeginDrawing(); +auto renderer::draw_textures(const int fps, const int ups, const size_t mass_count, + const size_t spring_count) const -> void +{ + BeginDrawing(); - DrawTextureRec(menu_target.texture, - Rectangle(0, 0, menu_target.texture.width, - -1 * menu_target.texture.height), - Vector2(0, 0), WHITE); - DrawTextureRec(klotski_target.texture, - Rectangle(0, 0, klotski_target.texture.width, - -1 * klotski_target.texture.height), - Vector2(0, MENU_HEIGHT), WHITE); - DrawTextureRec(render_target.texture, - Rectangle(0, 0, render_target.texture.width, - -1 * render_target.texture.height), - Vector2(GetScreenWidth() / 2.0, MENU_HEIGHT), WHITE); + DrawTextureRec(menu_target.texture, Rectangle(0, 0, menu_target.texture.width, -menu_target.texture.height), + Vector2(0, 0), WHITE); + DrawTextureRec(klotski_target.texture, + Rectangle(0, 0, klotski_target.texture.width, -klotski_target.texture.height), + Vector2(0, MENU_HEIGHT), WHITE); + DrawTextureRec(render_target.texture, Rectangle(0, 0, render_target.texture.width, -render_target.texture.height), + Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT), WHITE); - // Draw borders - DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0, - BLACK); - DrawRectangleLinesEx(Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0, - GetScreenHeight() - MENU_HEIGHT), - 1.0, BLACK); - DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0, MENU_HEIGHT, - GetScreenWidth() / 2.0, - GetScreenHeight() - MENU_HEIGHT), - 1.0, BLACK); + // Draw borders + DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0f, BLACK); + DrawRectangleLinesEx(Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f, + BLACK); + DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0f, MENU_HEIGHT, GetScreenWidth() / 2.0f, + GetScreenHeight() - MENU_HEIGHT), 1.0f, BLACK); - gui.DrawGraphOverlay(fps, ups); - gui.DrawSavePresetPopup(); - gui.Update(); + gui.draw_graph_overlay(fps, ups, mass_count, spring_count); + gui.draw_save_preset_popup(); + gui.update(); - EndDrawing(); + EndDrawing(); } + +auto renderer::render(const std::vector& masses, const int fps, const int ups, const size_t mass_count, + const size_t spring_count) -> void +{ + update_texture_sizes(); + + draw_mass_springs(masses); + draw_klotski(); + draw_menu(); + draw_textures(fps, ups, mass_count, spring_count); +} \ No newline at end of file diff --git a/src/state.cpp b/src/state.cpp deleted file mode 100644 index aa4e320..0000000 --- a/src/state.cpp +++ /dev/null @@ -1,292 +0,0 @@ -#include "state.hpp" -#include "config.hpp" -#include "distance.hpp" - -#include -#include -#include - -#ifdef TRACY -#include "tracy.hpp" -#include -#endif - -auto StateManager::ParsePresetFile(const std::string &_preset_file) -> bool { - preset_file = _preset_file; - - std::ifstream file(preset_file); - if (!file) { - std::cout << std::format("Preset file \"{}\" couldn't be loaded.", - preset_file) - << std::endl; - return false; - } - - std::string line; - std::vector comment_lines; - std::vector preset_lines; - while (std::getline(file, line)) { - if (line.starts_with("F") || line.starts_with("R")) { - preset_lines.push_back(line); - } else if (line.starts_with("#")) { - comment_lines.push_back(line); - } - } - - if (preset_lines.size() == 0 || comment_lines.size() != preset_lines.size()) { - std::cout << std::format("Preset file \"{}\" couldn't be loaded.", - preset_file) - << std::endl; - return false; - } - - presets.clear(); - for (const auto &preset : preset_lines) { - presets.emplace_back(preset); - } - comments = comment_lines; - - std::cout << std::format("Loaded {} presets from \"{}\".", - preset_lines.size(), preset_file) - << std::endl; - - return true; -} - -auto StateManager::AppendPresetFile(const std::string preset_name) -> void { - std::cout << std::format("Saving preset \"{}\" to \"{}\"", preset_name, - preset_file) - << std::endl; - - std::ofstream file(preset_file, std::ios_base::app | std::ios_base::out); - if (!file) { - std::cout << std::format("Preset file \"{}\" couldn't be loaded.", - preset_file) - << std::endl; - return; - } - - file << "\n# " << preset_name << "\n" << current_state.state << std::flush; - - std::cout << std::format("Refreshing presets...") << std::endl; - if (ParsePresetFile(preset_file)) { - LoadPreset(presets.size() - 1); - } -} - -auto StateManager::LoadPreset(int preset) -> void { - current_preset = preset; - current_state = presets.at(current_preset); - ClearGraph(); - edited = false; -} - -auto StateManager::ResetState() -> void { - current_state = presets.at(current_preset); - previous_state = current_state; - for (auto &[state, visits] : visited_states) { - visits = 0; - } - visited_states[current_state]++; - history = std::stack(); - total_moves = 0; - if (edited || !states.contains(current_state)) { - // We also need to clear the graph in case the state has been edited - // because the graph could contain states that are impossible to reach - // now. - ClearGraph(); - } - edited = false; -} - -auto StateManager::PreviousPreset() -> void { - LoadPreset((presets.size() + current_preset - 1) % presets.size()); -} - -auto StateManager::NextPreset() -> void { - LoadPreset((current_preset + 1) % presets.size()); -} - -auto StateManager::NextPath() -> void { - if (target_distances.Empty()) { - return; - } - - // Already there - if (target_distances.distances[CurrentMassIndex()] == 0) { - return; - } - - std::size_t parent = target_distances.parents[CurrentMassIndex()]; - current_state = masses.at(parent); - FindTargetPath(); -} - -auto StateManager::FillGraph() -> void { -#ifdef TRACY - ZoneScoped; -#endif - - ClearGraph(); - - std::pair, - std::vector>> - closure = current_state.Closure(); - - physics.ClearCmd(); - physics.AddMassSpringsCmd(closure.first.size(), closure.second); - for (const State &state : closure.first) { - states.insert(std::make_pair(state, states.size())); - masses.insert(std::make_pair(states.size() - 1, state)); - } - for (const auto &[from, to] : closure.second) { - springs.emplace_back(from, to); - } - FindWinningStates(); - FindTargetDistances(); - FindTargetPath(); - - // Sanity check. Both values need to be equal - // for (const auto &[mass, state] : masses) { - // std::cout << std::format("Masses: {}, States: {}", mass, - // states.at(state)) << std::endl; - // } -} - -auto StateManager::UpdateGraph() -> void { - if (previous_state == current_state) { - return; - } - - if (!states.contains(current_state)) { - states.insert(std::make_pair(current_state, states.size())); - masses.insert(std::make_pair(states.size() - 1, current_state)); - springs.emplace_back(states.at(current_state), states.at(previous_state)); - physics.AddMassCmd(); - physics.AddSpringCmd(states.at(current_state), states.at(previous_state)); - - if (current_state.IsWon()) { - winning_states.insert(current_state); - } - FindTargetDistances(); - } - - // Adds the element with 0 if it doesn't exist - visited_states[current_state]++; - total_moves++; - - if (history.size() > 0 && history.top() == current_state) { - // We don't pop the stack when moving backwards to indicate if we need to - // push or pop here - history.pop(); - } else { - history.push(previous_state); - } - - FindTargetPath(); - previous_state = current_state; -} - -auto StateManager::ClearGraph() -> void { - states.clear(); - winning_states.clear(); - visited_states.clear(); - masses.clear(); - winning_path.clear(); - springs.clear(); - // history = std::stack(); - target_distances.Clear(); - physics.ClearCmd(); - - // Re-add the default stuff to the graph - states.insert(std::make_pair(current_state, states.size())); - masses.insert(std::make_pair(states.size() - 1, current_state)); - visited_states.insert(std::make_pair(current_state, 1)); - physics.AddMassCmd(); - - // These states are no longer in the graph - previous_state = current_state; - starting_state = current_state; -} - -auto StateManager::FindWinningStates() -> void { - winning_states.clear(); - for (const auto &[state, mass] : states) { - if (state.IsWon()) { - winning_states.insert(state); - } - } -} - -auto StateManager::FindTargetDistances() -> void { -#ifdef TRACY - ZoneScoped; -#endif - - if (springs.size() == 0 || winning_states.size() == 0) { - return; - } - - // Find target indices - std::vector targets; - targets.reserve(winning_states.size()); - for (const auto &_state : winning_states) { - targets.push_back(states.at(_state)); - } - - target_distances = CalculateDistances(states.size(), springs, targets); - - // std::cout << std::format("Calculated {} distances to {} targets.", - // target_distances.distances.size(), targets.size()) << std::endl; -} - -auto StateManager::FindTargetPath() -> void { - if (target_distances.Empty()) { - return; - } - - winning_path = GetPath(target_distances, CurrentMassIndex()); - // std::cout << std::format("Nearest target is {} moves away.", - // winning_path.size()) << std::endl; -} - -auto StateManager::FindWorstState() -> State { - if (target_distances.Empty()) { - return current_state; - } - - int max = 0; - int index = 0; - for (std::size_t i = 0; i < target_distances.distances.size(); ++i) { - if (target_distances.distances.at(i) > max) { - max = target_distances.distances.at(i); - index = i; - } - } - - return masses.at(index); -} - -auto StateManager::GoToWorst() -> void { current_state = FindWorstState(); } - -auto StateManager::GoToNearestTarget() -> void { - if (target_distances.Empty()) { - return; - } - - current_state = - masses.at(target_distances.nearest_targets.at(CurrentMassIndex())); -} - -auto StateManager::PopHistory() -> void { - if (history.size() == 0) { - return; - } - - current_state = history.top(); - // history.pop(); // Done in UpdateGraph(); -} - -auto StateManager::CurrentMassIndex() const -> std::size_t { - return states.at(current_state); -} diff --git a/src/state_manager.cpp b/src/state_manager.cpp new file mode 100644 index 0000000..6f6e23d --- /dev/null +++ b/src/state_manager.cpp @@ -0,0 +1,485 @@ +#include "state_manager.hpp" +#include "distance.hpp" +#include "util.hpp" + +#include +#include + +#ifdef TRACY +#include +#endif + +auto state_manager::synced_try_insert_state(const puzzle& state) -> size_t +{ + if (state_indices.contains(state)) { + return state_indices.at(state); + } + + const size_t index = state_pool.size(); + state_pool.emplace_back(state); + state_indices.emplace(state, index); + visit_counts[index] = 0; + + // Queue an update to the physics engine state to keep in sync + physics.add_mass_cmd(); + + return index; +} + +auto state_manager::synced_insert_link(size_t first_index, size_t second_index) -> void +{ + links.emplace_back(first_index, second_index); + + // Queue an update to the physics engine state to keep in sync + physics.add_spring_cmd(first_index, second_index); +} + +auto state_manager::synced_insert_statespace(const std::vector& states, + const std::vector>& _links) -> void +{ + if (!state_pool.empty() || !state_indices.empty() || !links.empty()) { + warnln("Inserting statespace but collections haven't been cleared"); + } + + for (const puzzle& state : states) { + const size_t index = state_pool.size(); + state_pool.emplace_back(state); + state_indices.emplace(state, index); + visit_counts[index] = 0; + } + for (const auto& [from, to] : _links) { + links.emplace_back(from, to); + } + + // Queue an update to the physics engine state to keep in sync + physics.add_mass_springs_cmd(state_pool.size(), links); +} + +auto state_manager::synced_clear_statespace() -> void +{ + // Those are invalid without any states + current_state_index = -1; + previous_state_index = -1; + starting_state_index = -1; + + state_pool.clear(); + state_indices.clear(); + links.clear(); + node_target_distances.clear(); + winning_indices.clear(); + winning_path.clear(); + path_indices.clear(); + + // move_history does not get cleared here, but when resetting the board + visit_counts.clear(); + + // Queue an update to the physics engine state to keep in sync + physics.clear_cmd(); +} + +auto state_manager::parse_preset_file(const std::string& _preset_file) -> bool +{ + preset_file = _preset_file; + + std::ifstream file(preset_file); + if (!file) { + infoln("Preset file \"{}\" couldn't be loaded.", preset_file); + return false; + } + + std::string line; + std::vector comment_lines; + std::vector preset_lines; + while (std::getline(file, line)) { + if (line.starts_with("F") || line.starts_with("R")) { + preset_lines.push_back(line); + } else if (line.starts_with("#")) { + comment_lines.push_back(line); + } + } + + if (preset_lines.empty() || comment_lines.size() != preset_lines.size()) { + infoln("Preset file \"{}\" couldn't be loaded.", preset_file); + return false; + } + + preset_states.clear(); + for (const auto& preset : preset_lines) { + const puzzle& p = puzzle(preset); + + if (!p.valid_thorough()) { + preset_states = {puzzle(4, 5, 9, 9, false)}; + infoln("Preset file \"{}\" contained invalid presets.", preset_file); + return false; + } + preset_states.emplace_back(p); + } + preset_comments = comment_lines; + + infoln("Loaded {} presets from \"{}\".", preset_lines.size(), preset_file); + + return true; +} + +auto state_manager::append_preset_file(const std::string& preset_name) -> bool +{ + infoln(R"(Saving preset "{}" to "{}")", preset_name, preset_file); + + if (!get_current_state().valid_thorough()) { + return false; + } + + std::ofstream file(preset_file, std::ios_base::app | std::ios_base::out); + if (!file) { + infoln("Preset file \"{}\" couldn't be loaded.", preset_file); + return false; + } + + file << "\n# " << preset_name << "\n" << get_current_state().state << std::flush; + + infoln("Refreshing presets..."); + if (parse_preset_file(preset_file)) { + load_preset(preset_states.size() - 1); + } + + return true; +} + +auto state_manager::load_preset(const size_t preset) -> void +{ + clear_graph_and_add_current(preset_states.at(preset)); + current_preset = preset; +} + +auto state_manager::load_previous_preset() -> void +{ + load_preset((preset_states.size() + current_preset - 1) % preset_states.size()); +} + +auto state_manager::load_next_preset() -> void +{ + load_preset((current_preset + 1) % preset_states.size()); +} + +auto state_manager::update_current_state(const puzzle& p) -> void +{ + if (!p.valid()) { + return; + } + + const size_t size_before = state_pool.size(); + + // If state is a duplicate, index will be the existing index, + // if state is new, index will be state_pool.size() - 1 + const size_t index = synced_try_insert_state(p); + + // Because synced_insert_link does not check for duplicates we do it here, + // if the size grows, it was not a duplicate, and we can add the spring + if (state_pool.size() > size_before) { + // The order is important, as the position of the second mass will be updated depending on the first + synced_insert_link(current_state_index, index); + } + + previous_state_index = current_state_index; + current_state_index = index; + + if (current_state_index != previous_state_index) { + move_history.push(previous_state_index); + } + + if (p.won()) { + winning_indices.insert(current_state_index); + } + + // Adds the element with 0 if it doesn't exist + visit_counts[current_state_index]++; + total_moves++; + + // Recalculate distances only if the graph changed + if (state_pool.size() > size_before) { + populate_node_target_distances(); + } + populate_winning_path(); +} + +auto state_manager::edit_starting_state(const puzzle& p) -> void +{ + clear_graph_and_add_current(p); + + move_history = std::stack(); + total_moves = 0; + for (int& visits : visit_counts | std::views::values) { + visits = 0; + } + visit_counts[current_state_index]++; +} + +auto state_manager::goto_starting_state() -> void +{ + update_current_state(get_state(starting_state_index)); + + // Reset previous movement data since we're starting over (because we're fucking stupid) + previous_state_index = current_state_index; + for (int& visits : visit_counts | std::views::values) { + visits = 0; + } + visit_counts[current_state_index]++; + move_history = std::stack(); + total_moves = 0; +} + +auto state_manager::goto_optimal_next_state() -> void +{ + if (node_target_distances.empty()) { + return; + } + + // Already there + if (node_target_distances.distances[current_state_index] == 0) { + return; + } + + const size_t parent_index = node_target_distances.parents[current_state_index]; + update_current_state(get_state(parent_index)); +} + +auto state_manager::goto_previous_state() -> void +{ + if (move_history.empty()) { + return; + } + + update_current_state(get_state(move_history.top())); + + // Pop twice because update_current_state adds the state again... + move_history.pop(); + move_history.pop(); +} + +auto state_manager::goto_most_distant_state() -> void +{ + if (node_target_distances.empty()) { + return; + } + + int max_distance = 0; + size_t max_distance_index = 0; + for (size_t i = 0; i < node_target_distances.distances.size(); ++i) { + if (node_target_distances.distances.at(i) > max_distance) { + max_distance = node_target_distances.distances.at(i); + max_distance_index = i; + } + } + + update_current_state(get_state(max_distance_index)); +} + +auto state_manager::goto_closest_target_state() -> void +{ + if (node_target_distances.empty()) { + return; + } + + update_current_state(get_state(node_target_distances.nearest_targets.at(current_state_index))); +} + +auto state_manager::populate_graph() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + // Need to make a copy before clearing the state_pool + const puzzle p = get_current_state(); + + // Clear the graph first so we don't add duplicates somehow + synced_clear_statespace(); + + // Explore the entire statespace starting from the current state + const auto& [states, _links] = p.explore_state_space(); + synced_insert_statespace(states, _links); + + current_state_index = state_indices.at(p); + previous_state_index = current_state_index; + starting_state_index = current_state_index; + + // Search for cool stuff + populate_winning_indices(); + populate_node_target_distances(); + populate_winning_path(); +} + +auto state_manager::clear_graph_and_add_current(const puzzle& p) -> void +{ + // Need to make a copy before clearing the state_pool + const puzzle _p = p; + + synced_clear_statespace(); + + // Re-add the current state + current_state_index = synced_try_insert_state(_p); + + // These states are no longer in the graph + previous_state_index = current_state_index; + starting_state_index = current_state_index; + + visit_counts[current_state_index]++; +} + +auto state_manager::clear_graph_and_add_current() -> void +{ + clear_graph_and_add_current(get_current_state()); +} + +auto state_manager::populate_winning_indices() -> void +{ + winning_indices.clear(); + for (const auto& [state, index] : state_indices) { + if (state.won()) { + winning_indices.insert(index); + } + } +} + +auto state_manager::populate_node_target_distances() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + if (links.empty() || winning_indices.empty()) { + return; + } + + const std::vector targets(winning_indices.begin(), winning_indices.end()); + node_target_distances.calculate_distances(state_pool.size(), links, targets); +} + +auto state_manager::populate_winning_path() -> void +{ + if (node_target_distances.empty()) { + return; + } + + winning_path = node_target_distances.get_shortest_path(current_state_index); + + path_indices.clear(); + for (const size_t index : winning_path) { + path_indices.insert(index); + } +} + +auto state_manager::get_index(const puzzle& state) const -> size_t +{ + return state_indices.at(state); +} + +auto state_manager::get_current_index() const -> size_t +{ + return current_state_index; +} + +auto state_manager::get_starting_index() const -> size_t +{ + return starting_state_index; +} + +auto state_manager::get_state(const size_t index) const -> const puzzle& +{ + return state_pool.at(index); +} + +auto state_manager::get_current_state() const -> const puzzle& +{ + return get_state(current_state_index); +} + +auto state_manager::get_starting_state() const -> const puzzle& +{ + return get_state(starting_state_index); +} + +auto state_manager::get_state_count() const -> size_t +{ + return state_pool.size(); +} + +auto state_manager::get_target_count() const -> size_t +{ + return winning_indices.size(); +} + +auto state_manager::get_link_count() const -> size_t +{ + return links.size(); +} + +auto state_manager::get_path_length() const -> size_t +{ + return winning_path.size(); +} + +auto state_manager::get_links() const -> const std::vector>& +{ + return links; +} + +auto state_manager::get_winning_indices() const -> const std::unordered_set& +{ + return winning_indices; +} + +auto state_manager::get_visit_counts() const -> const std::unordered_map& +{ + return visit_counts; +} + +auto state_manager::get_winning_path() const -> const std::vector& +{ + return winning_path; +} + +auto state_manager::get_path_indices() const -> const std::unordered_set& +{ + return path_indices; +} + +auto state_manager::get_current_visits() const -> int +{ + return visit_counts.at(current_state_index); +} + +auto state_manager::get_current_preset() const -> size_t +{ + return current_preset; +} + +auto state_manager::get_preset_count() const -> size_t +{ + return preset_states.size(); +} + +auto state_manager::get_current_preset_comment() const -> const std::string& +{ + return preset_comments.at(current_preset); +} + +auto state_manager::has_history() const -> bool +{ + return !move_history.empty(); +} + +auto state_manager::has_distances() const -> bool +{ + return !node_target_distances.empty(); +} + +auto state_manager::get_total_moves() const -> size_t +{ + return total_moves; +} + +auto state_manager::was_edited() const -> bool +{ + return preset_states.at(current_preset) != get_state(starting_state_index); +} \ No newline at end of file diff --git a/src/tracy.cpp b/src/tracy.cpp deleted file mode 100644 index 37a0669..0000000 --- a/src/tracy.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "config.hpp" - -#ifdef TRACY - -#include "tracy.hpp" -#include - -void *operator new(std::size_t count) { - auto ptr = malloc(count); - TracyAllocS(ptr, count, 20); - return ptr; -} -void operator delete(void *ptr) noexcept { - TracyFreeS(ptr, 20); - free(ptr); -} -void operator delete(void *ptr, std::size_t count) noexcept { - TracyFreeS(ptr, 20); - free(ptr); -} - -#endif diff --git a/src/user_interface.cpp b/src/user_interface.cpp new file mode 100644 index 0000000..9b92524 --- /dev/null +++ b/src/user_interface.cpp @@ -0,0 +1,779 @@ +#include "user_interface.hpp" +#include "config.hpp" + +#include + +#define RAYGUI_IMPLEMENTATION +#include + +#ifdef TRACY +#include +#endif + +auto user_interface::grid::update_bounds(const int _x, const int _y, const int _width, const int _height, + const int _columns, const int _rows) -> void +{ + x = _x; + y = _y; + width = _width; + height = _height; + columns = _columns; + rows = _rows; +} + +auto user_interface::grid::update_bounds(const int _x, const int _y, const int _width, const int _height) -> void +{ + x = _x; + y = _y; + width = _width; + height = _height; +} + +auto user_interface::grid::update_bounds(const int _x, const int _y) -> void +{ + x = _x; + y = _y; +} + +auto user_interface::grid::bounds() const -> Rectangle +{ + Rectangle bounds{0, 0, static_cast(columns), static_cast(rows)}; + bounds.x -= padding; + bounds.y -= padding; + bounds.width += 2 * padding; + bounds.height += 2 * padding; + return bounds; +} + +auto user_interface::grid::bounds(const int _x, const int _y, const int _width, const int _height) const -> Rectangle +{ + if (_x < 0 || _x + _width > columns || _y < 0 || _y + _height > rows) { + errln("Grid bounds are outside range."); + exit(1); + } + + const int cell_width = (width - padding) / columns; + const int cell_height = (height - padding) / rows; + + return Rectangle(x + _x * cell_width + padding, y + _y * cell_height + padding, _width * cell_width - padding, + _height * cell_height - padding); +} + +auto user_interface::grid::square_bounds() const -> Rectangle +{ + Rectangle bounds = square_bounds(0, 0, columns, rows); + bounds.x -= padding; + bounds.y -= padding; + bounds.width += 2 * padding; + bounds.height += 2 * padding; + return bounds; +} + +auto user_interface::grid::square_bounds(const int _x, const int _y, const int _width, + const int _height) const -> Rectangle +{ + // Assumes each cell is square, so either width or height are not completely + // filled + + if (_x < 0 || _x + _width > columns || _y < 0 || _y + _height > rows) { + errln("Grid bounds are outside range."); + exit(1); + } + + const int available_width = width - padding * (columns + 1); + const int available_height = height - padding * (rows + 1); + const int cell_size = std::min(available_width / columns, available_height / rows); + + const int grid_width = cell_size * columns + padding * (columns + 1); + const int grid_height = cell_size * rows + padding * (rows + 1); + const int x_offset = (width - grid_width) / 2; + const int y_offset = (height - grid_height) / 2; + + return Rectangle(x_offset + _x * (cell_size + padding) + padding, y_offset + _y * (cell_size + padding) + padding, + _width * cell_size + padding * (_width - 1), _height * cell_size + padding * (_height - 1)); +} + +auto user_interface::init() -> void +{ + const Font font = LoadFontEx(FONT, FONT_SIZE, nullptr, 0); + SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); + GuiSetFont(font); + + default_style style = get_default_style(); + style.text_size = FONT_SIZE; + apply_color(style, GRAY); + + set_default_style(style); +} + +auto user_interface::apply_color(style& style, const Color color) -> void +{ + style.base_color_normal = ColorToInt(Fade(color, 0.8)); + style.base_color_focused = ColorToInt(Fade(color, 0.3)); + style.base_color_pressed = ColorToInt(Fade(color, 0.8)); + style.base_color_disabled = ColorToInt(Fade(color, 0.5)); + + style.border_color_normal = ColorToInt(Fade(color, 1.0)); + style.border_color_focused = ColorToInt(Fade(color, 0.7)); + style.border_color_pressed = ColorToInt(Fade(color, 1.0)); + style.border_color_disabled = ColorToInt(Fade(GRAY, 0.5)); + + style.text_color_normal = ColorToInt(Fade(BLACK, 1.0)); + style.text_color_focused = ColorToInt(Fade(BLACK, 1.0)); + style.text_color_pressed = ColorToInt(Fade(BLACK, 1.0)); + style.text_color_disabled = ColorToInt(Fade(BLACK, 0.5)); +} + +auto user_interface::apply_block_color(style& style, const Color color) -> void +{ + style.base_color_normal = ColorToInt(Fade(color, 0.5)); + style.base_color_focused = ColorToInt(Fade(color, 0.3)); + style.base_color_pressed = ColorToInt(Fade(color, 0.8)); + style.base_color_disabled = ColorToInt(Fade(color, 0.5)); + + style.border_color_normal = ColorToInt(Fade(color, 1.0)); + style.border_color_focused = ColorToInt(Fade(color, 0.7)); + style.border_color_pressed = ColorToInt(Fade(color, 1.0)); + style.border_color_disabled = ColorToInt(Fade(GRAY, 0.5)); +} + +auto user_interface::apply_text_color(style& style, const Color color) -> void +{ + style.text_color_normal = ColorToInt(Fade(color, 1.0)); + style.text_color_focused = ColorToInt(Fade(color, 1.0)); + style.text_color_pressed = ColorToInt(Fade(color, 1.0)); + style.text_color_disabled = ColorToInt(Fade(BLACK, 0.5)); +} + +auto user_interface::get_default_style() -> default_style +{ + // Could've iterated over the values, but then it wouldn't be as nice to + // access... + return { + { + GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL), GuiGetStyle(DEFAULT, BASE_COLOR_NORMAL), + GuiGetStyle(DEFAULT, TEXT_COLOR_NORMAL), GuiGetStyle(DEFAULT, BORDER_COLOR_FOCUSED), + GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED), GuiGetStyle(DEFAULT, TEXT_COLOR_FOCUSED), + GuiGetStyle(DEFAULT, BORDER_COLOR_PRESSED), GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED), + GuiGetStyle(DEFAULT, TEXT_COLOR_PRESSED), GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED), + GuiGetStyle(DEFAULT, BASE_COLOR_DISABLED), GuiGetStyle(DEFAULT, TEXT_COLOR_DISABLED) + }, + GuiGetStyle(DEFAULT, BACKGROUND_COLOR), GuiGetStyle(DEFAULT, LINE_COLOR), GuiGetStyle(DEFAULT, TEXT_SIZE), + GuiGetStyle(DEFAULT, TEXT_SPACING), GuiGetStyle(DEFAULT, TEXT_LINE_SPACING), + GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL), GuiGetStyle(DEFAULT, TEXT_WRAP_MODE) + }; +} + +auto user_interface::set_default_style(const default_style& style) -> void +{ + GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, style.border_color_normal); + GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, style.base_color_normal); + GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, style.text_color_normal); + + GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, style.border_color_focused); + GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, style.base_color_focused); + GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, style.text_color_focused); + + GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, style.border_color_pressed); + GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, style.base_color_pressed); + GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, style.text_color_pressed); + + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, style.border_color_disabled); + GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, style.base_color_disabled); + GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, style.text_color_disabled); + + GuiSetStyle(DEFAULT, BACKGROUND_COLOR, style.background_color); + GuiSetStyle(DEFAULT, LINE_COLOR, style.line_color); + + GuiSetStyle(DEFAULT, TEXT_SIZE, style.text_size); + GuiSetStyle(DEFAULT, TEXT_SPACING, style.text_spacing); + GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, style.text_line_spacing); + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, style.text_alignment_vertical); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, style.text_wrap_mode); +} + +auto user_interface::get_component_style(const int component) -> component_style +{ + return { + { + GuiGetStyle(component, BORDER_COLOR_NORMAL), GuiGetStyle(component, BASE_COLOR_NORMAL), + GuiGetStyle(component, TEXT_COLOR_NORMAL), GuiGetStyle(component, BORDER_COLOR_FOCUSED), + GuiGetStyle(component, BASE_COLOR_FOCUSED), GuiGetStyle(component, TEXT_COLOR_FOCUSED), + GuiGetStyle(component, BORDER_COLOR_PRESSED), GuiGetStyle(component, BASE_COLOR_PRESSED), + GuiGetStyle(component, TEXT_COLOR_PRESSED), GuiGetStyle(component, BORDER_COLOR_DISABLED), + GuiGetStyle(component, BASE_COLOR_DISABLED), GuiGetStyle(component, TEXT_COLOR_DISABLED) + }, + GuiGetStyle(component, BORDER_WIDTH), GuiGetStyle(component, TEXT_PADDING), + GuiGetStyle(component, TEXT_ALIGNMENT) + }; +} + +auto user_interface::set_component_style(const int component, const component_style& style) -> void +{ + GuiSetStyle(component, BORDER_COLOR_NORMAL, style.border_color_normal); + GuiSetStyle(component, BASE_COLOR_NORMAL, style.base_color_normal); + GuiSetStyle(component, TEXT_COLOR_NORMAL, style.text_color_normal); + + GuiSetStyle(component, BORDER_COLOR_FOCUSED, style.border_color_focused); + GuiSetStyle(component, BASE_COLOR_FOCUSED, style.base_color_focused); + GuiSetStyle(component, TEXT_COLOR_FOCUSED, style.text_color_focused); + + GuiSetStyle(component, BORDER_COLOR_PRESSED, style.border_color_pressed); + GuiSetStyle(component, BASE_COLOR_PRESSED, style.base_color_pressed); + GuiSetStyle(component, TEXT_COLOR_PRESSED, style.text_color_pressed); + + GuiSetStyle(component, BORDER_COLOR_DISABLED, style.border_color_disabled); + GuiSetStyle(component, BASE_COLOR_DISABLED, style.base_color_disabled); + GuiSetStyle(component, TEXT_COLOR_DISABLED, style.text_color_disabled); + + GuiSetStyle(component, BORDER_WIDTH, style.border_width); + GuiSetStyle(component, TEXT_PADDING, style.text_padding); + GuiSetStyle(component, TEXT_ALIGNMENT, style.text_alignment); +} + +auto user_interface::draw_button(const Rectangle bounds, const std::string& label, const Color color, + const bool enabled, const int font_size) const -> int +{ + // Save original styling + const default_style original_default = get_default_style(); + const component_style original_button = get_component_style(BUTTON); + + // Change styling + default_style style_default = original_default; + component_style style_button = original_button; + style_default.text_size = font_size; + apply_color(style_button, color); + set_default_style(style_default); + set_component_style(BUTTON, style_button); + + const int _state = GuiGetState(); + if (!enabled || window_open()) { + GuiSetState(STATE_DISABLED); + } + const int pressed = GuiButton(bounds, label.data()); + if (!enabled || window_open()) { + GuiSetState(_state); + } + + // Restore original styling + set_default_style(original_default); + set_component_style(BUTTON, original_button); + + return pressed; +} + +auto user_interface::draw_menu_button(const int x, const int y, const int width, const int height, + const std::string& label, const Color color, const bool enabled, + const int font_size) const -> int +{ + const Rectangle bounds = menu_grid.bounds(x, y, width, height); + return draw_button(bounds, label, color, enabled, font_size); +} + +auto user_interface::draw_toggle_slider(const Rectangle bounds, const std::string& off_label, + const std::string& on_label, int* active, Color color, bool enabled, + int font_size) const -> int +{ + // Save original styling + const default_style original_default = get_default_style(); + const component_style original_slider = get_component_style(SLIDER); + const component_style original_toggle = get_component_style(TOGGLE); + + // Change styling + default_style style_default = original_default; + component_style style_slider = original_slider; + component_style style_toggle = original_toggle; + style_default.text_size = font_size; + apply_color(style_slider, color); + apply_color(style_toggle, color); + set_default_style(style_default); + set_component_style(SLIDER, style_slider); + set_component_style(TOGGLE, style_toggle); + + const int _state = GuiGetState(); + if (!enabled || window_open()) { + GuiSetState(STATE_DISABLED); + } + int pressed = GuiToggleSlider(bounds, std::format("{};{}", off_label, on_label).data(), active); + if (!enabled || window_open()) { + GuiSetState(_state); + } + + // Restore original styling + set_default_style(original_default); + set_component_style(SLIDER, original_slider); + set_component_style(TOGGLE, original_toggle); + + return pressed; +} + +auto user_interface::draw_menu_toggle_slider(const int x, const int y, const int width, const int height, + const std::string& off_label, const std::string& on_label, int* active, + const Color color, const bool enabled, const int font_size) const -> int +{ + const Rectangle bounds = menu_grid.bounds(x, y, width, height); + return draw_toggle_slider(bounds, off_label, on_label, active, color, enabled, font_size); +} + +auto user_interface::draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max, Color color, + bool enabled, int font_size) const -> int +{ + // Save original styling + const default_style original_default = get_default_style(); + const component_style original_valuebox = get_component_style(VALUEBOX); + const component_style original_button = get_component_style(BUTTON); + + // Change styling + default_style style_default = original_default; + component_style style_valuebox = original_valuebox; + component_style style_button = original_button; + style_default.text_size = font_size; + apply_color(style_valuebox, color); + apply_color(style_button, color); + set_default_style(style_default); + set_component_style(VALUEBOX, style_valuebox); + set_component_style(BUTTON, style_button); + + const int _state = GuiGetState(); + if (!enabled || window_open()) { + GuiSetState(STATE_DISABLED); + } + int pressed = GuiSpinner(bounds, "", label.data(), value, min, max, false); + if (!enabled || window_open()) { + GuiSetState(_state); + } + + // Restore original styling + set_default_style(original_default); + set_component_style(VALUEBOX, original_valuebox); + set_component_style(BUTTON, style_button); + + return pressed; +} + +auto user_interface::draw_menu_spinner(const int x, const int y, const int width, const int height, + const std::string& label, int* value, const int min, const int max, + const Color color, const bool enabled, const int font_size) const -> int +{ + const Rectangle bounds = menu_grid.bounds(x, y, width, height); + return draw_spinner(bounds, label, value, min, max, color, enabled, font_size); +} + +auto user_interface::draw_label(const Rectangle bounds, const std::string& text, const Color color, const bool enabled, + const int font_size) const -> int +{ + // Save original styling + const default_style original_default = get_default_style(); + const component_style original_label = get_component_style(LABEL); + + // Change styling + default_style style_default = original_default; + component_style style_label = original_label; + style_default.text_size = font_size; + apply_text_color(style_label, color); + set_default_style(style_default); + set_component_style(LABEL, style_label); + + const int _state = GuiGetState(); + if (!enabled || window_open()) { + GuiSetState(STATE_DISABLED); + } + const int pressed = GuiLabel(bounds, text.data()); + if (!enabled || window_open()) { + GuiSetState(_state); + } + + // Restore original styling + set_default_style(original_default); + set_component_style(LABEL, original_label); + + return pressed; +} + +auto user_interface::draw_board_block(const int x, const int y, const int width, const int height, const Color color, + const bool enabled) const -> bool +{ + component_style s = get_component_style(BUTTON); + apply_block_color(s, color); + + const Rectangle bounds = board_grid.square_bounds(x, y, width, height); + + const bool focused = CheckCollisionPointRec(input.mouse - Vector2(0, MENU_HEIGHT), bounds); + const bool pressed = puzzle::block(x, y, width, height, false).covers(input.sel_x, input.sel_y); + + // Background to make faded colors work + DrawRectangleRec(bounds, RAYWHITE); + + Color base = GetColor(s.base_color_normal); + Color border = GetColor(s.base_color_normal); + if (pressed) { + base = GetColor(s.base_color_pressed); + border = GetColor(s.base_color_pressed); + } + if (focused) { + base = GetColor(s.base_color_focused); + border = GetColor(s.base_color_focused); + } + if (focused && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { + base = GetColor(s.base_color_pressed); + border = GetColor(s.base_color_pressed); + } + if (!enabled) { + base = BOARD_COLOR_RESTRICTED; + } + DrawRectangleRec(bounds, base); + if (enabled) { + DrawRectangleLinesEx(bounds, 2.0, border); + } + + return focused && enabled; +} + +auto user_interface::window_open() const -> bool +{ + return save_window || help_window; +} + +auto user_interface::draw_menu_header(const Color color) const -> void +{ + int preset = state.get_current_preset(); + draw_menu_spinner(0, 0, 1, 1, "Preset: ", &preset, -1, state.get_preset_count(), color, !input.editing); + if (preset > static_cast(state.get_current_preset())) { + input.load_next_preset(); + } else if (preset < static_cast(state.get_current_preset())) { + input.load_previous_preset(); + } + + draw_menu_button(1, 0, 1, 1, std::format("Puzzle: \"{}\"{}", state.get_current_preset_comment().substr(2), + state.was_edited() ? " (Modified)" : ""), color); + + int editing = input.editing; + draw_menu_toggle_slider(2, 0, 1, 1, "Puzzle Mode (Tab)", "Edit Mode (Tab)", &editing, color); + if (editing != input.editing) { + input.toggle_editing(); + } +} + +auto user_interface::draw_graph_info(const Color color) const -> void +{ + draw_menu_button(0, 1, 1, 1, std::format("Found {} States ({} Winning)", state.get_state_count(), + state.get_target_count()), color); + + draw_menu_button(1, 1, 1, 1, std::format("Found {} Transitions", state.get_link_count()), color); + + draw_menu_button(2, 1, 1, 1, std::format("{} Moves to Nearest Solution", + state.get_path_length() > 0 ? state.get_path_length() - 1 : 0), color); +} + +auto user_interface::draw_graph_controls(const Color color) const -> void +{ + if (draw_menu_button(0, 2, 1, 1, "Populate Graph (G)", color)) { + input.populate_graph(); + } + + // int mark_path = input.mark_path; + // DrawMenuToggleSlider(2, 2, 1, 1, "Path Hidden (U)", "Path Shown (U)", + // &mark_path, color); + // if (mark_path != input.mark_path) { + // input.ToggleMarkPath(); + // } + + if (draw_menu_button(1, 2, 1, 1, "Clear Graph (C)", color)) { + input.clear_graph(); + } + + int mark_solutions = input.mark_solutions; + draw_menu_toggle_slider(2, 2, 1, 1, "Solution Hidden (I)", "Solution Shown (I)", &mark_solutions, color); + if (mark_solutions != input.mark_solutions) { + input.toggle_mark_solutions(); + } + input.mark_path = input.mark_solutions; +} + +auto user_interface::draw_camera_controls(const Color color) const -> void +{ + int lock_camera = input.camera_lock; + draw_menu_toggle_slider(0, 3, 1, 1, "Free Camera (L)", "Locked Camera (L)", &lock_camera, color); + if (lock_camera != input.camera_lock) { + input.toggle_camera_lock(); + } + + int lock_camera_mass_center = input.camera_mass_center_lock; + draw_menu_toggle_slider(1, 3, 1, 1, "Current Block (U)", "Graph Center (U)", &lock_camera_mass_center, color, + input.camera_lock); + if (lock_camera_mass_center != input.camera_mass_center_lock) { + input.toggle_camera_mass_center_lock(); + } + + int projection = camera.projection == CAMERA_ORTHOGRAPHIC; + draw_menu_toggle_slider(2, 3, 1, 1, "Perspective (Alt)", "Orthographic (Alt)", &projection, color); + if (projection != (camera.projection == CAMERA_ORTHOGRAPHIC)) { + input.toggle_camera_projection(); + } +} + +auto user_interface::draw_puzzle_controls(const Color color) const -> void +{ + auto nth = [&](const int n) + { + if (n == 11 || n == 12 || n == 13) { + return "th"; + } + if (n % 10 == 1) { + return "st"; + } + if (n % 10 == 2) { + return "nd"; + } + if (n % 10 == 3) { + return "rd"; + } + return "th"; + }; + + const int visits = state.get_current_visits(); + draw_menu_button(0, 4, 1, 1, std::format("{} Moves ({}{} Time at this State)", state.get_total_moves(), visits, + nth(visits)), color); + + if (draw_menu_button(1, 4, 1, 1, "Make Optimal Move (Space)", color, state.has_distances())) { + input.goto_optimal_next_state(); + } + + if (draw_menu_button(2, 4, 1, 1, "Undo Last Move (Backspace)", color, state.has_history())) { + input.goto_previous_state(); + } + + if (draw_menu_button(0, 5, 1, 1, "Go to Nearest Solution (B)", color, state.has_distances())) { + input.goto_closest_target_state(); + } + + if (draw_menu_button(1, 5, 1, 1, "Go to Worst State (V)", color, state.has_distances())) { + input.goto_most_distant_state(); + } + + if (draw_menu_button(2, 5, 1, 1, "Go to Starting State (R)", color)) { + input.goto_starting_state(); + } +} + +auto user_interface::draw_edit_controls(const Color color) const -> void +{ + const puzzle& current = state.get_current_state(); + + // Toggle Target Block + if (draw_menu_button(0, 4, 1, 1, "Toggle Target Block (T)", color)) { + input.toggle_target_block(); + } + + // Toggle Wall Block + if (draw_menu_button(0, 5, 1, 1, "Toggle Wall Block (Y)", color)) { + input.toggle_wall_block(); + } + + // Toggle Restricted/Free Block Movement + int free = !current.restricted; + draw_menu_toggle_slider(1, 4, 1, 1, "Restricted (F)", "Free (F)", &free, color); + if (free != !current.restricted) { + input.toggle_restricted_movement(); + } + + // Clear Goal + if (draw_menu_button(1, 5, 1, 1, "Clear Goal (X)", color)) {} + + // Column Count Spinner + int columns = current.width; + draw_menu_spinner(2, 4, 1, 1, "Cols: ", &columns, puzzle::MIN_WIDTH, puzzle::MAX_WIDTH, color); + if (columns > current.width) { + input.add_board_column(); + } else if (columns < current.width) { + input.remove_board_column(); + } + + // Row Count Spinner + int rows = current.height; + draw_menu_spinner(2, 5, 1, 1, "Rows: ", &rows, puzzle::MIN_WIDTH, puzzle::MAX_WIDTH, color); + if (rows > current.height) { + input.add_board_row(); + } else if (rows < current.height) { + input.remove_board_row(); + } +} + +auto user_interface::draw_menu_footer(const Color color) -> void +{ + draw_menu_button(0, 6, 2, 1, std::format("State: \"{}\"", state.get_current_state().state), color); + + if (draw_menu_button(2, 6, 1, 1, "Save as Preset", color)) { + save_window = true; + } +} + +auto user_interface::get_background_color() -> Color +{ + return GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)); +} + +auto user_interface::help_popup() -> void {} + +auto user_interface::draw_save_preset_popup() -> void +{ + if (!save_window) { + return; + } + + // Returns the pressed button index + const int button = GuiTextInputBox( + Rectangle((GetScreenWidth() - POPUP_WIDTH) / 2.0f, (GetScreenHeight() - POPUP_HEIGHT) / 2.0f, POPUP_WIDTH, + POPUP_HEIGHT), "Save as Preset", "Enter Preset Name", "Ok;Cancel", preset_name.data(), 255, nullptr); + if (button == 1) { + state.append_preset_file(preset_name.data()); + } + if (button == 0 || button == 1 || button == 2) { + save_window = false; + TextCopy(preset_name.data(), "\0"); + } +} + +auto user_interface::draw_main_menu() -> void +{ + menu_grid.update_bounds(0, 0, GetScreenWidth(), MENU_HEIGHT); + + draw_menu_header(GRAY); + draw_graph_info(ORANGE); + draw_graph_controls(RED); + draw_camera_controls(DARKGREEN); + + if (input.editing) { + draw_edit_controls(PURPLE); + } else { + draw_puzzle_controls(BLUE); + } + + draw_menu_footer(GRAY); +} + +auto user_interface::draw_puzzle_board() -> void +{ + const puzzle& current = state.get_current_state(); + + board_grid.update_bounds(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT, current.width, + current.height); + + // Draw outer border + const Rectangle bounds = board_grid.square_bounds(); + DrawRectangleRec(bounds, current.won() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED); + + // Draw inner borders + DrawRectangle(bounds.x + BOARD_PADDING, bounds.y + BOARD_PADDING, bounds.width - 2 * BOARD_PADDING, + bounds.height - 2 * BOARD_PADDING, current.restricted ? BOARD_COLOR_RESTRICTED : BOARD_COLOR_FREE); + + // Draw target opening + // TODO: Only draw single direction (in corner) if restricted (use target block principal direction) + const std::optional target_block = current.try_get_target_block(); + if (current.has_win_condition() && target_block.has_value()) { + const int target_x = current.target_x; + const int target_y = current.target_y; + auto [x, y, width, height] = board_grid.square_bounds(target_x, target_y, target_block.value().width, + target_block.value().height); + + const Color opening_color = Fade(current.won() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED, 0.3); + + if (target_x == 0) { + // Left opening + DrawRectangle(x - BOARD_PADDING, y, BOARD_PADDING, height, RAYWHITE); + DrawRectangle(x - BOARD_PADDING, y, BOARD_PADDING, height, opening_color); + } + if (target_x + target_block.value().width == current.width) { + // Right opening + DrawRectangle(x + width, y, BOARD_PADDING, height, RAYWHITE); + DrawRectangle(x + width, y, BOARD_PADDING, height, opening_color); + } + if (target_y == 0) { + // Top opening + DrawRectangle(x, y - BOARD_PADDING, width, BOARD_PADDING, RAYWHITE); + DrawRectangle(x, y - BOARD_PADDING, width, BOARD_PADDING, opening_color); + } + if (target_y + target_block.value().height == current.height) { + // Bottom opening + DrawRectangle(x, y + height, width, BOARD_PADDING, RAYWHITE); + DrawRectangle(x, y + height, width, BOARD_PADDING, opening_color); + } + } + + // Draw empty cells. Also set hovered blocks + input.hov_x = -1; + input.hov_y = -1; + for (int x = 0; x < board_grid.columns; ++x) { + for (int y = 0; y < board_grid.rows; ++y) { + DrawRectangleRec(board_grid.square_bounds(x, y, 1, 1), RAYWHITE); + + Rectangle hov_bounds = board_grid.square_bounds(x, y, 1, 1); + hov_bounds.x -= BOARD_PADDING; + hov_bounds.y -= BOARD_PADDING; + hov_bounds.width += BOARD_PADDING; + hov_bounds.height += BOARD_PADDING; + if (CheckCollisionPointRec(GetMousePosition() - Vector2(0, MENU_HEIGHT), hov_bounds)) { + input.hov_x = x; + input.hov_y = y; + } + } + } + + // Draw blocks + for (const puzzle::block& b : current) { + Color c = BLOCK_COLOR; + if (b.target) { + c = TARGET_BLOCK_COLOR; + } else if (b.immovable) { + c = WALL_COLOR; + } + + if (!b.valid() || b.x < 0 || b.y < 0 || b.width <= 0 || b.height <= 0) { + warnln("Iterator returned invalid block for state \"{}\".", current.state); + continue; + } + + // ReSharper disable once CppExpressionWithoutSideEffects + draw_board_block(b.x, b.y, b.width, b.height, c, !b.immovable); + } + + // Draw block placing + if (input.editing && input.has_block_add_xy) { + if (current.covers(input.block_add_x, input.block_add_y) && input.hov_x >= input.block_add_x && input.hov_y >= + input.block_add_y) { + bool collides = false; + for (const puzzle::block& b : current) { + if (b.collides(puzzle::block(input.block_add_x, input.block_add_y, input.hov_x - input.block_add_x + 1, + input.hov_y - input.block_add_y + 1, false))) { + collides = true; + break; + } + } + if (!collides) { + draw_board_block(input.block_add_x, input.block_add_y, input.hov_x - input.block_add_x + 1, + input.hov_y - input.block_add_y + 1, PURPLE); + } + } + } +} + +auto user_interface::draw_graph_overlay(int fps, int ups, size_t mass_count, size_t spring_count) -> void +{ + graph_overlay_grid.update_bounds(GetScreenWidth() / 2, MENU_HEIGHT); + debug_overlay_grid.update_bounds(GetScreenWidth() / 2, GetScreenHeight() - 75); + + draw_label(graph_overlay_grid.bounds(0, 0, 1, 1), std::format("Dist: {:0>7.2f}", camera.distance), BLACK); + draw_label(graph_overlay_grid.bounds(0, 1, 1, 1), std::format("FoV: {:0>6.2f}", camera.fov), BLACK); + draw_label(graph_overlay_grid.bounds(0, 2, 1, 1), std::format("FPS: {:0>3}", fps), LIME); + draw_label(graph_overlay_grid.bounds(0, 3, 1, 1), std::format("UPS: {:0>3}", ups), ORANGE); + + // Debug + draw_label(debug_overlay_grid.bounds(0, 0, 1, 1), std::format("Debug:"), BLACK); + draw_label(debug_overlay_grid.bounds(0, 1, 1, 1), std::format("Masses: {}", mass_count), BLACK); + draw_label(debug_overlay_grid.bounds(0, 2, 1, 1), std::format("Springs: {}", spring_count), BLACK); +} + +auto user_interface::update() const -> void +{ + input.disable = window_open(); +} \ No newline at end of file