diff --git a/.gitmodules b/.gitmodules index ea94d4e..5927242 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/BLT-With-Graphics-Template"] path = lib/BLT-With-Graphics-Template url = https://git.tpgc.me/tri11paragon/BLT-With-Graphics-Template +[submodule "lib/json"] + path = lib/json + url = https://github.com/nlohmann/json.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 53b241d..42e7b48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(graphs VERSION 0.1.1) +project(graphs VERSION 0.1.2) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) @@ -11,6 +11,8 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(lib/BLT-With-Graphics-Template SYSTEM) +set(JSON_BuildTests OFF CACHE INTERNAL "") +add_subdirectory(lib/json) include_directories(include/) file(GLOB_RECURSE PROJECT_BUILD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") @@ -21,6 +23,7 @@ target_compile_options(graphs PRIVATE -Wall -Wextra -Wno-comment) target_link_options(graphs PRIVATE -Wall -Wextra -Wno-comment) target_link_libraries(graphs PUBLIC BLT_WITH_GRAPHICS) +target_link_libraries(graphs PRIVATE nlohmann_json::nlohmann_json) if (${ENABLE_ADDRSAN} MATCHES ON) target_compile_options(graphs PRIVATE -fsanitize=address) diff --git a/include/config.h b/include/config.h index 82eca05..96646de 100644 --- a/include/config.h +++ b/include/config.h @@ -29,6 +29,7 @@ namespace conf inline constexpr float DEFAULT_SPRING_LENGTH = 175.0; inline constexpr float DEFAULT_INITIAL_TEMPERATURE = 100; + inline constexpr float POINT_SIZE = 35; inline constexpr auto DEFAULT_IMAGE = "parker_point"; inline constexpr auto POINT_OUTLINE_COLOR = blt::make_color(0, 0.6, 0.6); inline constexpr auto POINT_HIGHLIGHT_COLOR = blt::make_color(0, 1.0, 1.0); @@ -38,4 +39,7 @@ namespace conf inline constexpr auto EDGE_OUTLINE_COLOR = blt::make_color(0, 1, 0); } +class graph_t; +struct loader_t; + #endif //GRAPHS_CONFIG_H diff --git a/include/graph.h b/include/graph.h index 6d50303..2ef4ce2 100644 --- a/include/graph.h +++ b/include/graph.h @@ -52,8 +52,10 @@ struct bounding_box class graph_t { + friend struct loader_t; private: std::vector nodes; + blt::hashmap_t names_to_node; blt::hashset_t edges; blt::hashmap_t> connected_nodes; bool sim = false; @@ -64,7 +66,6 @@ class graph_t int current_iterations = 0; int max_iterations = 5000; std::unique_ptr equation; - static constexpr float POINT_SIZE = 35; blt::i32 selected_node = -1; blt::quad_easing easing; @@ -74,12 +75,14 @@ class graph_t blt::f64 scaling_connectivity, blt::f64 distance_factor); public: - graph_t() = default; + graph_t() + { + use_Eades(); + } void make_new(const bounding_box& bb, const blt::size_t min_nodes, const blt::size_t max_nodes, const blt::f64 connectivity) { create_random_graph(bb, min_nodes, max_nodes, connectivity, 0, 25); - use_Eades(); } void reset(const bounding_box& bb, const blt::size_t min_nodes, const blt::size_t max_nodes, const blt::f64 connectivity, @@ -198,6 +201,7 @@ class graph_t class engine_t { + friend struct loader_t; private: graph_t graph; diff --git a/include/graph_base.h b/include/graph_base.h index 6d2cd8b..18077de 100644 --- a/include/graph_base.h +++ b/include/graph_base.h @@ -27,6 +27,8 @@ struct node { float repulsiveness = 24.0f; + std::string name; + blt::gfx::point2d_t point; float outline_scale = 1.25f; blt::vec2 velocity; diff --git a/include/loader.h b/include/loader.h new file mode 100644 index 0000000..2097c5b --- /dev/null +++ b/include/loader.h @@ -0,0 +1,45 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GRAPHS_LOADER_H +#define GRAPHS_LOADER_H + +#include +#include +#include +#include +#include +#include + +struct loader_t +{ + std::vector> textures; + + /** + * if save path is present and a valid file it will be read from, otherwise path will be used as the default. + */ + static std::optional load_for(engine_t& engine, const blt::gfx::window_data& data, std::string_view path, + std::optional save_path = {}); + + /** + * Saves engine to the path. Will also save any extra loader data like textures. Loader can be empty. + */ + static void save_for(engine_t& engine, const loader_t& loader, std::string_view path); +}; + +#endif //GRAPHS_LOADER_H diff --git a/lib/json b/lib/json new file mode 160000 index 0000000..960b763 --- /dev/null +++ b/lib/json @@ -0,0 +1 @@ +Subproject commit 960b763ecd144f156d05ec61f577b04107290137 diff --git a/src/graph.cpp b/src/graph.cpp index dff5a0f..c5c5f9a 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -94,7 +94,7 @@ void graph_t::process_mouse_drag(const blt::i32 width, const blt::i32 height) const auto mag = dist.magnitude(); - if (mag < POINT_SIZE && mouse_pressed) + if (mag < conf::POINT_SIZE && mouse_pressed) { new_selection = static_cast(index); break; @@ -137,10 +137,10 @@ void graph_t::create_random_graph(bounding_box bb, const blt::size_t min_nodes, // don't allow points too close to the edges of the window. if (bb.is_screen) { - bb.max_x -= POINT_SIZE; - bb.max_y -= POINT_SIZE; - bb.min_x += POINT_SIZE; - bb.min_y += POINT_SIZE; + bb.max_x -= conf::POINT_SIZE; + bb.max_y -= conf::POINT_SIZE; + bb.min_x += conf::POINT_SIZE; + bb.min_y += conf::POINT_SIZE; } static std::random_device dev; static std::uniform_real_distribution chance(0.0, 1.0); @@ -164,7 +164,7 @@ void graph_t::create_random_graph(bounding_box bb, const blt::size_t min_nodes, const float dx = rp.x() - x; const float dy = rp.y() - y; - if (const float dist = std::sqrt(dx * dx + dy * dy); dist <= POINT_SIZE) + if (const float dist = std::sqrt(dx * dx + dy * dy); dist <= conf::POINT_SIZE) { can_break = false; break; @@ -173,7 +173,7 @@ void graph_t::create_random_graph(bounding_box bb, const blt::size_t min_nodes, if (can_break) break; } while (true); - nodes.push_back(node({x, y, POINT_SIZE})); + nodes.push_back(node({x, y, conf::POINT_SIZE})); } for (const auto& [index1, node1] : blt::enumerate(nodes)) diff --git a/src/loader.cpp b/src/loader.cpp new file mode 100644 index 0000000..9d66b11 --- /dev/null +++ b/src/loader.cpp @@ -0,0 +1,94 @@ +/* + * + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +template +T load_with_default(const json& data, std::string_view key, T def) +{ + if (data.contains(key)) + return data[key].get(); + return def; +} + +std::optional loader_t::load_for(engine_t& engine, const blt::gfx::window_data& window_data, std::string_view path, + std::optional save_path) +{ + auto& graph = engine.graph; + + static std::random_device dev; + std::uniform_real_distribution pos_x_dist(0.0, static_cast(window_data.width)); + std::uniform_real_distribution pos_y_dist(0.0, static_cast(window_data.height)); + + if (save_path && std::filesystem::exists(*save_path)) + path = *save_path; + if (!std::filesystem::exists(path)) + { + BLT_WARN("Unable to load graph file!"); + return {}; + } + std::ifstream file{std::string(path)}; + json data = json::parse(file); + + loader_t loader; + if (data.contains("textures")) + { + for (const auto& texture : data["textures"]) + { + auto texture_path = texture["path"].get(); + auto texture_key = load_with_default(texture, "name", texture_path); + loader.textures.emplace_back(texture_key, texture_path); + } + } + + if (data.contains("nodes")) + { + graph.nodes.clear(); + + for (const auto& node : data["nodes"]) + { + auto x = static_cast(load_with_default(node, "x", pos_x_dist(dev))); + auto y = static_cast(load_with_default(node, "y", pos_y_dist(dev))); + auto size = static_cast(load_with_default(node, "size", static_cast(conf::POINT_SIZE))); + auto name = load_with_default(node, "name", "unnamed"); + + graph.names_to_node.insert({name, static_cast(graph.nodes.size())}); + graph.nodes.emplace_back(blt::gfx::point2d_t{x, y, size}); + graph.nodes.back().name = std::move(name); + } + } + + if (data.contains("connections")) + { + graph.edges.clear(); + graph.connected_nodes.clear(); + } + + return loader; +} + +void loader_t::save_for(engine_t& engine, const loader_t& loader, std::string_view path) +{ + +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4cd6a4c..2471155 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ #include #include #include +#include blt::gfx::matrix_state_manager global_matrices; blt::gfx::resource_manager resources; @@ -42,6 +43,7 @@ namespace im = ImGui; * - ability to remove nodes from graph (make use of multi selection?) */ engine_t engine; +loader_t loader_data; void init(const blt::gfx::window_data& data) { @@ -53,6 +55,13 @@ void init(const blt::gfx::window_data& data) resources.enqueue("res/parkerpoint.png", "parker_point"); resources.enqueue("res/parker cat ears.jpg", "parkercat"); + if (auto loader = loader_t::load_for(engine, data, "default.json", "save.json")) + { + loader_data = *loader; + for (const auto& [key, path] : loader_data.textures) + resources.enqueue(path, key); + } + global_matrices.create_internals(); resources.load_resources(); renderer_2d.create(); @@ -78,6 +87,9 @@ void update(const blt::gfx::window_data& data) int main(int, const char**) { blt::gfx::init(blt::gfx::window_data{"Force-Directed Graph Drawing", init, update, 1440, 720}.setSyncInterval(1)); + + loader_t::save_for(engine, loader_data, "save.json"); + global_matrices.cleanup(); resources.cleanup(); renderer_2d.cleanup();