diff --git a/include/force_algorithms.h b/include/force_algorithms.h index 785129c..9356999 100644 --- a/include/force_algorithms.h +++ b/include/force_algorithms.h @@ -29,7 +29,7 @@ class force_equation { public: - using node_pair = const std::pair&; + using node_pair = const std::pair&; protected: float cooling_rate = conf::DEFAULT_COOLING_FACTOR; float min_cooling = conf::DEFAULT_MIN_COOLING; @@ -53,7 +53,7 @@ class force_equation public: - [[nodiscard]] virtual blt::vec2 attr(node_pair v1, node_pair v2, const edge& edge) const = 0; + [[nodiscard]] virtual blt::vec2 attr(node_pair v1, node_pair v2, const edge_t& edge) const = 0; [[nodiscard]] virtual blt::vec2 rep(node_pair v1, node_pair v2) const = 0; @@ -77,7 +77,7 @@ class Eades_equation : public force_equation protected: float spring_constant = 12.0; public: - [[nodiscard]] blt::vec2 attr(node_pair v1, node_pair v2, const edge& edge) const final; + [[nodiscard]] blt::vec2 attr(node_pair v1, node_pair v2, const edge_t& edge) const final; [[nodiscard]] blt::vec2 rep(node_pair v1, node_pair v2) const final; @@ -92,7 +92,7 @@ class Eades_equation : public force_equation class Fruchterman_Reingold_equation : public force_equation { public: - [[nodiscard]] blt::vec2 attr(node_pair v1, node_pair v2, const edge& edge) const final; + [[nodiscard]] blt::vec2 attr(node_pair v1, node_pair v2, const edge_t& edge) const final; [[nodiscard]] blt::vec2 rep(node_pair v1, node_pair v2) const final; diff --git a/include/graph.h b/include/graph.h index d9ce146..8557715 100644 --- a/include/graph.h +++ b/include/graph.h @@ -54,9 +54,9 @@ class graph_t { friend struct loader_t; private: - std::vector nodes; + std::vector nodes; blt::hashmap_t names_to_node; - blt::hashset_t edges; + blt::hashset_t edges; blt::hashmap_t> connected_nodes; bool sim = false; bool run_infinitely = true; @@ -67,7 +67,10 @@ class graph_t int max_iterations = 5000; std::unique_ptr equation; - blt::i32 selected_node = -1; + blt::i64 last_selected_node = -1; + blt::i64 last_selected_node2 = -1; + bool is_edge = false; + blt::quad_easing easing; blt::quint_easing highlight_easing; @@ -100,6 +103,7 @@ class graph_t nodes.clear(); edges.clear(); connected_nodes.clear(); + names_to_node.clear(); } void connect(const blt::u64 n1, const blt::u64 n2) @@ -109,16 +113,16 @@ class graph_t edges.insert({n1, n2}); } - void connect(const edge& edge) + void connect(const edge_t& edge) { connected_nodes[edge.getFirst()].insert(edge.getSecond()); connected_nodes[edge.getSecond()].insert(edge.getFirst()); edges.insert(edge); } - [[nodiscard]] std::optional> connected(blt::u64 n1, blt::u64 n2) const + [[nodiscard]] std::optional> connected(blt::u64 n1, blt::u64 n2) const { - auto itr = edges.find(edge{n1, n2}); + auto itr = edges.find(edge_t{n1, n2}); if (itr == edges.end()) return {}; return *itr; @@ -126,15 +130,7 @@ class graph_t void render(); - void reset_mouse_drag() - { - if (selected_node != -1) - { - nodes[selected_node].outline_color = conf::POINT_OUTLINE_COLOR; - easing.reset(); - } - selected_node = -1; - } + void reset_mouse_drag(); void reset_mouse_highlight() { diff --git a/include/graph_base.h b/include/graph_base.h index 195f9ec..04853b5 100644 --- a/include/graph_base.h +++ b/include/graph_base.h @@ -24,10 +24,17 @@ #include #include -struct node +inline blt::size_t last_node = 0; + +inline std::string get_name() +{ + return "unnamed" + std::to_string(last_node++); +} + +struct node_t { float repulsiveness = 24.0f; - std::string name = "unnamed"; + std::string name; std::string description; std::string texture = conf::DEFAULT_IMAGE; @@ -36,9 +43,14 @@ struct node float outline_scale = 1.25f; blt::color4 outline_color = conf::POINT_OUTLINE_COLOR; - explicit node(const blt::gfx::point2d_t& point): point(point) + explicit node_t(const blt::gfx::point2d_t& point): name(get_name()), point(point) {} + explicit node_t(const blt::gfx::point2d_t& point, std::string name): name(std::move(name)), point(point) + { + BLT_ASSERT(!this->name.empty() && "Name should not be empty!"); + } + blt::vec2& getVelocityRef() { return velocity; @@ -61,19 +73,19 @@ struct node }; -struct edge +struct edge_t { float ideal_spring_length = conf::DEFAULT_SPRING_LENGTH; float thickness = conf::DEFAULT_THICKNESS; blt::color4 color = conf::EDGE_COLOR; std::string description; - edge(blt::u64 i1, blt::u64 i2): i1(i1), i2(i2) + edge_t(blt::u64 i1, blt::u64 i2): i1(i1), i2(i2) { BLT_ASSERT(i1 != i2 && "Indices cannot be equal!"); } - inline friend bool operator==(edge e1, edge e2) + inline friend bool operator==(edge_t e1, edge_t e2) { return (e1.i1 == e2.i1 || e1.i1 == e2.i2) && (e1.i2 == e2.i1 || e1.i2 == e2.i2); } @@ -95,7 +107,7 @@ struct edge struct edge_hash { - blt::u64 operator()(const edge& e) const + blt::u64 operator()(const edge_t& e) const { return e.getFirst() * e.getSecond(); } @@ -103,7 +115,7 @@ struct edge_hash struct edge_eq { - bool operator()(const edge& e1, const edge& e2) const + bool operator()(const edge_t& e1, const edge_t& e2) const { return e1 == e2; } diff --git a/src/force_algorithms.cpp b/src/force_algorithms.cpp index ea4d995..5c94a91 100644 --- a/src/force_algorithms.cpp +++ b/src/force_algorithms.cpp @@ -31,7 +31,7 @@ BLT_ATTRIB_NO_SIDE_EFFECTS inline float mix(const float v1, const float v2) * -------------------------------------------------------- */ -force_equation::equation_data force_equation::calc_data(const std::pair& v1, const std::pair& v2) +force_equation::equation_data force_equation::calc_data(const std::pair& v1, const std::pair& v2) { auto dir = dir_v(v1, v2); auto dir2 = dir_v(v2, v1); @@ -57,13 +57,13 @@ void force_equation::draw_inputs_base() * -------------------------------------------------------- */ -blt::vec2 Eades_equation::attr(const std::pair& v1, const std::pair& v2, const edge& edge) const +blt::vec2 Eades_equation::attr(const std::pair& v1, const std::pair& v2, const edge_t& edge) const { auto data = calc_data(v1, v2); return (spring_constant * std::log(data.mag / edge.ideal_spring_length) * data.unit) - rep(v1, v2); } -blt::vec2 Eades_equation::rep(const std::pair& v1, const std::pair& v2) const +blt::vec2 Eades_equation::rep(const std::pair& v1, const std::pair& v2) const { auto data = calc_data(v1, v2); // scaling factor included because of the scales this algorithm is working on (large viewport) @@ -83,14 +83,14 @@ void Eades_equation::draw_inputs() * -------------------------------------------------------- */ -blt::vec2 Fruchterman_Reingold_equation::attr(const std::pair& v1, const std::pair& v2, const edge& edge) const +blt::vec2 Fruchterman_Reingold_equation::attr(const std::pair& v1, const std::pair& v2, const edge_t& edge) const { auto data = calc_data(v1, v2); float scale = data.mag_sq / edge.ideal_spring_length; return (scale * data.unit); } -blt::vec2 Fruchterman_Reingold_equation::rep(const std::pair& v1, const std::pair& v2) const +blt::vec2 Fruchterman_Reingold_equation::rep(const std::pair& v1, const std::pair& v2) const { auto data = calc_data(v1, v2); const auto ideal_spring_length = conf::DEFAULT_SPRING_LENGTH; diff --git a/src/graph.cpp b/src/graph.cpp index 4c33f2b..77788e9 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -83,13 +83,14 @@ void graph_t::render() } } +// called every frame as long as imgui doesn't require the mouse. void graph_t::process_mouse_drag(const blt::i32 width, const blt::i32 height) { const auto mouse_pos = blt::make_vec2(blt::gfx::calculateRay2D(width, height, global_matrices.getScale2D(), global_matrices.getView2D(), global_matrices.getOrtho())); bool mouse_pressed = blt::gfx::isMousePressed(0); - blt::i32 new_selection = -1; + blt::i64 new_selection = -1; for (const auto& [index, node] : blt::enumerate(nodes)) { @@ -98,34 +99,34 @@ void graph_t::process_mouse_drag(const blt::i32 width, const blt::i32 height) const auto mag = dist.magnitude(); - if (mag < conf::POINT_SIZE && mouse_pressed) + if (mag < node.getRenderObj().scale && mouse_pressed) { - new_selection = static_cast(index); + new_selection = static_cast(index); break; } } - if (new_selection != selected_node) + if (new_selection != last_selected_node) { - if (selected_node != -1) - nodes[selected_node].outline_color = conf::POINT_OUTLINE_COLOR; + if (last_selected_node != -1) + nodes[last_selected_node].outline_color = conf::POINT_OUTLINE_COLOR; } - if (mouse_pressed && new_selection == -1 && selected_node != -1) + if (mouse_pressed && new_selection == -1 && last_selected_node != -1) { - selected_node = -1; + last_selected_node = -1; } - selected_node = new_selection; + last_selected_node = new_selection; if (!mouse_pressed && blt::gfx::mouseReleaseLastFrame()) { reset_mouse_drag(); } - if (selected_node != -1 && mouse_pressed) + if (last_selected_node != -1 && mouse_pressed) { - auto& node = nodes[selected_node]; + auto& node = nodes[last_selected_node]; easing.progress(8 * static_cast(blt::gfx::getFrameDeltaSeconds())); node.outline_color = conf::POINT_SELECT_COLOR; node.getPositionRef() = mouse_pos; @@ -177,7 +178,8 @@ 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, conf::POINT_SIZE})); + nodes.push_back(node_t({x, y, conf::POINT_SIZE})); + names_to_node.insert({nodes.back().name, nodes.size() - 1}); } for (const auto& [index1, node1] : blt::enumerate(nodes)) @@ -219,10 +221,22 @@ void graph_t::create_random_graph(bounding_box bb, const blt::size_t min_nodes, } } +void graph_t::reset_mouse_drag() +{ + if (last_selected_node != -1) + { + nodes[last_selected_node].outline_color = conf::POINT_OUTLINE_COLOR; + easing.reset(); + } + last_selected_node = -1; +} + void engine_t::draw_gui(const blt::gfx::window_data& data) { double ft = blt::gfx::getFrameDeltaSeconds(); - if (im::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + im::SetNextWindowPos({0, 0}, ImGuiCond_Always); + im::SetNextWindowSize({350, static_cast(data.height)}); + if (im::Begin("Info", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { static int min_nodes = 5; static int max_nodes = 25; @@ -235,7 +249,6 @@ void engine_t::draw_gui(const blt::gfx::window_data& data) //im::SetNextItemOpen(true, ImGuiCond_Once); im::Text("FPS: %lf Frame-time (ms): %lf Frame-time (S): %lf", 1.0 / ft, ft * 1000.0, ft); - im::Text("Number of Nodes: %d", graph.numberOfNodes()); im::SetNextItemOpen(true, ImGuiCond_Once); if (im::CollapsingHeader("Help")) { @@ -244,6 +257,7 @@ void engine_t::draw_gui(const blt::gfx::window_data& data) } if (im::CollapsingHeader("Graph Generation Settings")) { + im::Text("Current number of Nodes: %d", graph.numberOfNodes()); im::Checkbox("Screen Auto-Scale", &bb.is_screen); if (im::CollapsingHeader("Spawning Area")) { @@ -273,7 +287,6 @@ void engine_t::draw_gui(const blt::gfx::window_data& data) graph.reset(bb, min_nodes, max_nodes, connectivity, scaling_connectivity, distance_factor); } } - im::SetNextItemOpen(true, ImGuiCond_Once); if (im::CollapsingHeader("Simulation Settings")) { im::InputInt("Max Iterations", &graph.getMaxIterations()); @@ -318,6 +331,11 @@ void engine_t::draw_gui(const blt::gfx::window_data& data) break; } } + } + im::SetNextItemOpen(true, ImGuiCond_Once); + if (im::CollapsingHeader("Node Information")) + { + } im::End(); } diff --git a/src/loader.cpp b/src/loader.cpp index 9c348eb..6ef664c 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include @@ -70,12 +69,12 @@ std::optional loader_t::load_for(engine_t& engine, const blt::gfx::win 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"); + auto name = load_with_default(node, "name", get_name()); auto texture = load_with_default(node, "texture", conf::DEFAULT_IMAGE); + BLT_ASSERT(!graph.names_to_node.contains(name) && "Graph node name must be unique!"); 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); + graph.nodes.emplace_back(blt::gfx::point2d_t{x, y, size}, std::move(name)); graph.nodes.back().texture = std::move(texture); } } @@ -99,7 +98,7 @@ std::optional loader_t::load_for(engine_t& engine, const blt::gfx::win auto ideal_length = load_with_default(edge, "length", conf::DEFAULT_SPRING_LENGTH); auto thickness = load_with_default(edge, "thickness", conf::DEFAULT_THICKNESS); - ::edge e{graph.names_to_node[index1], graph.names_to_node[index2]}; + ::edge_t e{graph.names_to_node[index1], graph.names_to_node[index2]}; e.ideal_spring_length = ideal_length; e.thickness = thickness; @@ -113,6 +112,8 @@ std::optional loader_t::load_for(engine_t& engine, const blt::gfx::win { if (auto node = graph.names_to_node.find(desc["name"].get()); node != graph.names_to_node.end()) graph.nodes[node->second].description = desc["description"]; + else + BLT_WARN("Node %s doesn't exist!", desc["name"].get().c_str()); } } @@ -122,14 +123,15 @@ std::optional loader_t::load_for(engine_t& engine, const blt::gfx::win { auto nodes = desc["nodes"]; auto n1 = graph.names_to_node[nodes[0].get()]; - auto n2 = graph.names_to_node[nodes[2].get()]; + auto n2 = graph.names_to_node[nodes[1].get()]; if (auto node = graph.edges.find({n1, n2}); node != graph.edges.end()) { - edge e = *node; + edge_t e = *node; e.description = desc["description"]; graph.edges.erase({n1, n2}); graph.edges.insert(e); - } + } else + BLT_WARN("Edge %s -> %s doesn't exist!", nodes[0].get().c_str(), nodes[1].get().c_str()); } } @@ -138,5 +140,50 @@ std::optional loader_t::load_for(engine_t& engine, const blt::gfx::win void loader_t::save_for(engine_t& engine, const loader_t& loader, std::string_view path) { - + auto& graph = engine.graph; + json data; + data["textures"] = json::array(); + data["nodes"] = json::array(); + data["edges"] = json::array(); + data["descriptions"] = json::array(); + data["relationships"] = json::array(); + + for (const auto& [key, t_path] : loader.textures) + { + data["textures"].push_back(json{ + {"name", key}, + {"path", t_path} + }); + } + + for (const auto& node : graph.nodes) + { + data["nodes"].push_back(json{ + {"name", node.name}, + {"texture", node.texture}, + {"size", node.getRenderObj().scale}, + {"x", node.getPosition().x()}, + {"y", node.getPosition().y()}, + }); + data["descriptions"].push_back(json{ + {"name", node.name}, + {"description", node.description} + }); + } + + for (const auto& edge : graph.edges) + { + data["edges"].push_back(json{ + {"nodes", json::array_t{graph.nodes[edge.getFirst()].name, graph.nodes[edge.getSecond()].name}}, + {"length", edge.ideal_spring_length}, + {"thickness", edge.thickness} + }); + data["relationships"].push_back(json{ + {"nodes", json::array_t{graph.nodes[edge.getFirst()].name, graph.nodes[edge.getSecond()].name}}, + {"description", edge.description} + }); + } + + std::ofstream file{std::string(path)}; + file << data.dump(4); } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 2471155..ddfd560 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -60,13 +60,12 @@ void init(const blt::gfx::window_data& data) loader_data = *loader; for (const auto& [key, path] : loader_data.textures) resources.enqueue(path, key); - } + } else + engine.init(data); global_matrices.create_internals(); resources.load_resources(); renderer_2d.create(); - - engine.init(data); } void update(const blt::gfx::window_data& data)