diff --git a/CMakeLists.txt b/CMakeLists.txt index 030095a..1a81d80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(graphs VERSION 0.0.40) +project(graphs VERSION 0.0.41) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) diff --git a/include/graph.h b/include/graph.h new file mode 100644 index 0000000..31647c4 --- /dev/null +++ b/include/graph.h @@ -0,0 +1,200 @@ +#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_GRAPH_H +#define GRAPHS_GRAPH_H + +#include +#include +#include + +namespace im = ImGui; + +struct bounding_box +{ + int min_x = 0; + int min_y = 0; + int max_x = 0; + int max_y = 0; + + bounding_box(const int min_x, const int min_y, const int max_x, const int max_y): min_x(min_x), min_y(min_y), max_x(max_x), max_y(max_y) + { + } + + bool is_screen = true; +}; + +class graph_t +{ + private: + std::vector nodes; + blt::hashset_t edges; + blt::hashmap_t> connected_nodes; + bool sim = false; + bool run_infinitely = true; + float sim_speed = 1; + float threshold = 0; + float max_force_last = 1; + int current_iterations = 0; + int max_iterations = 5000; + std::unique_ptr equation; + static constexpr float POINT_SIZE = 35; + + blt::i32 current_node = -1; + + void create_random_graph(bounding_box bb, blt::size_t min_nodes, blt::size_t max_nodes, blt::f64 connectivity, + blt::f64 scaling_connectivity, blt::f64 distance_factor); + + public: + graph_t() = default; + + 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, + const blt::f64 scaling_connectivity, const blt::f64 distance_factor) + { + sim = false; + current_iterations = 0; + max_force_last = 1.0; + nodes.clear(); + edges.clear(); + connected_nodes.clear(); + create_random_graph(bb, min_nodes, max_nodes, connectivity, scaling_connectivity, distance_factor); + } + + void connect(const blt::u64 n1, const blt::u64 n2) + { + edges.insert(edge{n1, n2}); + connected_nodes[n1].insert(n2); + connected_nodes[n2].insert(n1); + } + + [[nodiscard]] bool connected(blt::u64 e1, blt::u64 e2) const + { + return edges.contains({e1, e2}); + } + + void render(double frame_time); + + void reset_mouse_drag() + { + current_node = -1; + } + + void process_mouse_drag(blt::i32 width, blt::i32 height); + + void use_Eades() + { + equation = std::make_unique(); + } + + void use_Fruchterman_Reingold() + { + equation = std::make_unique(); + } + + void start_sim() + { + sim = true; + } + + void stop_sim() + { + sim = false; + } + + [[nodiscard]] std::string getSimulatorName() const + { + return equation->name(); + } + + [[nodiscard]] auto* getSimulator() const + { + return equation.get(); + } + + [[nodiscard]] auto getCoolingFactor() const + { + return equation->cooling_factor(current_iterations); + } + + void reset_iterations() + { + current_iterations = 0; + } + + [[nodiscard]] bool& getIterControl() + { + return run_infinitely; + } + + [[nodiscard]] float& getSimSpeed() + { + return sim_speed; + } + + [[nodiscard]] float& getThreshold() + { + return threshold; + } + + [[nodiscard]] int& getMaxIterations() + { + return max_iterations; + } + + [[nodiscard]] int numberOfNodes() const + { + return static_cast(nodes.size()); + } +}; + +class engine_t +{ + private: + graph_t graph; + + void draw_gui(const blt::gfx::window_data& data, double ft); + + public: + void init(const blt::gfx::window_data& data) + { + graph.make_new({0, 0, data.width, data.height}, 5, 25, 0.2); + } + + void render(const blt::gfx::window_data& data, const double ft) + { + draw_gui(data, ft); + + auto& io = ImGui::GetIO(); + + if (!io.WantCaptureMouse && blt::gfx::isMousePressed(0)) + graph.process_mouse_drag(data.width, data.height); + else + graph.reset_mouse_drag(); + + + graph.render(ft); + } +}; + +#endif //GRAPHS_GRAPH_H diff --git a/lib/BLT-With-Graphics-Template b/lib/BLT-With-Graphics-Template index af14354..8ec4c2c 160000 --- a/lib/BLT-With-Graphics-Template +++ b/lib/BLT-With-Graphics-Template @@ -1 +1 @@ -Subproject commit af143549cbbafa26ad10b89777093d55e5d82bde +Subproject commit 8ec4c2c30b0a6d313ca5df5ff83210fd4f553908 diff --git a/src/graph.cpp b/src/graph.cpp new file mode 100644 index 0000000..f187c2f --- /dev/null +++ b/src/graph.cpp @@ -0,0 +1,286 @@ +/* + * + * 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 + +extern blt::gfx::batch_renderer_2d renderer_2d; +extern blt::gfx::matrix_state_manager global_matrices; + +int sub_ticks = 1; + +void graph_t::render(const double frame_time) +{ + if (sim && (current_iterations < max_iterations || run_infinitely) && max_force_last > threshold) + { + for (int _ = 0; _ < sub_ticks; _++) + { + // calculate new forces + for (const auto& v1 : blt::enumerate(nodes)) + { + blt::vec2 attractive; + blt::vec2 repulsive; + for (const auto& v2 : blt::enumerate(nodes)) + { + if (v1.first == v2.first) + continue; + if (connected(v1.first, v2.first)) + attractive += equation->attr(v1, v2); + repulsive += equation->rep(v1, v2); + } + v1.second.getVelocityRef() = attractive + repulsive; + } + max_force_last = 0; + // update positions + for (auto& v : nodes) + { + const float sim_factor = static_cast(frame_time * sim_speed) * 0.05f; + v.getPositionRef() += v.getVelocityRef() * equation->cooling_factor(current_iterations) * sim_factor; + max_force_last = std::max(max_force_last, v.getVelocityRef().magnitude()); + } + current_iterations++; + } + } + + for (const auto& point : nodes) + renderer_2d.drawPointInternal(blt::gfx::render_info_t::make_info("parker_point", point.getOutlineColor(), point.getOutlineScale()), + point.getRenderObj(), 15.0f); + for (const auto& edge : edges) + { + if (edge.getFirst() >= nodes.size() || edge.getSecond() >= nodes.size()) + { + BLT_WARN("Edge Error %ld %ld %ld", edge.getFirst(), edge.getSecond(), nodes.size()); + } else + { + auto n1 = nodes[edge.getFirst()]; + auto n2 = nodes[edge.getSecond()]; + auto draw_info = blt::gfx::render_info_t::make_info(edge.getColor(), edge.getOutlineColor(), edge.getOutlineScale()); + renderer_2d.drawLine(draw_info, 5.0f, n1.getRenderObj().pos, n2.getRenderObj().pos, edge.getThickness()); + } + } +} + +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())); + + if (current_node < 0) + { + for (const auto& [index, node] : blt::enumerate(nodes)) + { + const auto pos = node.getPosition(); + const auto dist = pos - mouse_pos; + + if (const auto mag = dist.magnitude(); mag < POINT_SIZE) + { + current_node = static_cast(index); + break; + } + } + } else + nodes[current_node].getPositionRef() = mouse_pos; +} + +void graph_t::create_random_graph(bounding_box bb, const blt::size_t min_nodes, const blt::size_t max_nodes, const blt::f64 connectivity, + const blt::f64 scaling_connectivity, const blt::f64 distance_factor) +{ + // 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; + } + static std::random_device dev; + static std::uniform_real_distribution chance(0.0, 1.0); + std::uniform_int_distribution node_count_dist(min_nodes, max_nodes); + std::uniform_real_distribution pos_x_dist(static_cast(bb.min_x), static_cast(bb.max_x)); + std::uniform_real_distribution pos_y_dist(static_cast(bb.min_y), static_cast(bb.max_y)); + + const auto node_count = node_count_dist(dev); + + for (blt::size_t i = 0; i < node_count; i++) + { + float x, y; + do + { + bool can_break = true; + x = pos_x_dist(dev); + y = pos_y_dist(dev); + for (const auto& node : nodes) + { + const auto& rp = node.getRenderObj().pos; + 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) + { + can_break = false; + break; + } + } + if (can_break) + break; + } while (true); + nodes.push_back(node({x, y, POINT_SIZE})); + } + + for (const auto& [index1, node1] : blt::enumerate(nodes)) + { + for (const auto& [index2, node2] : blt::enumerate(nodes)) + { + if (index1 == index2) + continue; + const auto diff = node2.getPosition() - node1.getPosition(); + const auto diff_sq = (diff * diff); + const auto dist = distance_factor / static_cast(std::sqrt(diff_sq.x() + diff_sq.y())); + double dexp; + if (dist == 0) + dexp = 0; + else + dexp = 1 / (std::exp(dist) - dist); + if (const auto rand = chance(dev); rand <= connectivity && rand >= dexp * scaling_connectivity) + connect(index1, index2); + } + } + + std::uniform_int_distribution node_select_dist(0ul, nodes.size() - 1); + for (blt::size_t i = 0; i < nodes.size(); i++) + { + if (connected_nodes[i].size() <= 1) + { + for (blt::size_t j = connected_nodes[i].size(); j < 2; j++) + { + blt::u64 select; + do + { + select = node_select_dist(dev); + if (select != i && !connected_nodes[i].contains(select)) + break; + } while (true); + connect(i, select); + } + } + } +} + +void engine_t::draw_gui(const blt::gfx::window_data& data, const double ft) +{ + if (im::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + static int min_nodes = 5; + static int max_nodes = 25; + + static bounding_box bb{0, 0, data.width, data.height}; + + static float connectivity = 0.12; + static float scaling_connectivity = 0.5; + static float distance_factor = 100; + + //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")) + { + im::Text("You can use W/A/S/D to move the camera around"); + im::Text("Q/E can be used to zoom in/out the camera"); + } + if (im::CollapsingHeader("Graph Generation Settings")) + { + im::Checkbox("Screen Auto-Scale", &bb.is_screen); + if (im::CollapsingHeader("Spawning Area")) + { + bool result = false; + result |= im::InputInt("Min X", &bb.min_x, 5, 100); + result |= im::InputInt("Max X", &bb.max_x, 5, 100); + result |= im::InputInt("Min Y", &bb.min_y, 5, 100); + result |= im::InputInt("Max Y", &bb.max_y, 5, 100); + if (result) + bb.is_screen = false; + } + if (bb.is_screen) + { + bb.max_x = data.width; + bb.max_y = data.height; + bb.min_x = 0; + bb.min_y = 0; + } + im::SeparatorText("Node Settings"); + im::InputInt("Min Nodes", &min_nodes); + im::InputInt("Max Nodes", &max_nodes); + im::SliderFloat("Connectivity", &connectivity, 0, 1); + im::SliderFloat("Scaling Connectivity", &scaling_connectivity, 0, 1); + im::InputFloat("Distance Factor", &distance_factor, 5, 100); + if (im::Button("Reset Graph")) + { + 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()); + im::Checkbox("Run Infinitely", &graph.getIterControl()); + im::InputInt("Sub-ticks Per Frame", &sub_ticks); + im::InputFloat("Threshold", &graph.getThreshold(), 0.01, 1); + graph.getSimulator()->draw_inputs_base(); + graph.getSimulator()->draw_inputs(); + im::Text("Current Cooling Factor: %f", graph.getCoolingFactor()); + im::SliderFloat("Simulation Speed", &graph.getSimSpeed(), 0, 4); + } + im::SetNextItemOpen(true, ImGuiCond_Once); + if (im::CollapsingHeader("System Controls")) + { + if (im::Button("Start")) + graph.start_sim(); + im::SameLine(); + if (im::Button("Stop")) + graph.stop_sim(); + if (im::Button("Reset Iterations")) + graph.reset_iterations(); + im::Text("Select a system:"); + const auto current_sim = graph.getSimulatorName(); + const char* items[] = {"Eades", "Fruchterman & Reingold"}; + static int item_current = 0; + ImGui::ListBox("##SillyBox", &item_current, items, 2, 2); + + if (strcmp(items[item_current], current_sim.c_str()) != 0) + { + switch (item_current) + { + case 0: + graph.use_Eades(); + BLT_INFO("Using Eades"); + break; + case 1: + graph.use_Fruchterman_Reingold(); + BLT_INFO("Using Fruchterman & Reingold"); + break; + default: + BLT_WARN("This is not a valid selection! How did we get here?"); + break; + } + } + } + im::End(); + } +} diff --git a/src/main.cpp b/src/main.cpp index 60fda94..e14e9fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,8 +29,7 @@ #include #include #include -#include -#include +#include #include blt::gfx::matrix_state_manager global_matrices; @@ -39,434 +38,9 @@ blt::gfx::batch_renderer_2d renderer_2d(resources, global_matrices); blt::gfx::first_person_camera_2d camera; blt::u64 lastTime; double ft = 0; -double fps = 0; -int sub_ticks = 1; namespace im = ImGui; -struct bounding_box -{ - int min_x = 0; - int min_y = 0; - int max_x = 0; - int max_y = 0; - - bounding_box(const int min_x, const int min_y, const int max_x, const int max_y): min_x(min_x), min_y(min_y), max_x(max_x), max_y(max_y) - { - } - - bool is_screen = true; -}; - -class graph_t -{ - private: - std::vector nodes; - blt::hashset_t edges; - blt::hashmap_t > connected_nodes; - bool sim = false; - bool run_infinitely = true; - float sim_speed = 1; - float threshold = 0; - float max_force_last = 1; - int current_iterations = 0; - int max_iterations = 5000; - std::unique_ptr equation; - static constexpr float POINT_SIZE = 35; - - blt::i32 current_node = -1; - - void create_random_graph(bounding_box bb, const blt::size_t min_nodes, const blt::size_t max_nodes, const blt::f64 connectivity, - const blt::f64 scaling_connectivity, const blt::f64 distance_factor) - { - // 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; - } - static std::random_device dev; - static std::uniform_real_distribution chance(0.0, 1.0); - std::uniform_int_distribution node_count_dist(min_nodes, max_nodes); - std::uniform_real_distribution pos_x_dist(static_cast(bb.min_x), static_cast(bb.max_x)); - std::uniform_real_distribution pos_y_dist(static_cast(bb.min_y), static_cast(bb.max_y)); - - const auto node_count = node_count_dist(dev); - - for (blt::size_t i = 0; i < node_count; i++) - { - float x, y; - do - { - bool can_break = true; - x = pos_x_dist(dev); - y = pos_y_dist(dev); - for (const auto& node : nodes) - { - const auto& rp = node.getRenderObj().pos; - 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) - { - can_break = false; - break; - } - } - if (can_break) - break; - } while (true); - nodes.push_back(node({x, y, POINT_SIZE})); - } - - for (const auto& [index1, node1] : blt::enumerate(nodes)) - { - for (const auto& [index2, node2] : blt::enumerate(nodes)) - { - if (index1 == index2) - continue; - const auto diff = node2.getPosition() - node1.getPosition(); - const auto diff_sq = (diff * diff); - const auto dist = distance_factor / static_cast(std::sqrt(diff_sq.x() + diff_sq.y())); - double dexp; - if (dist == 0) - dexp = 0; - else - dexp = 1 / (std::exp(dist) - dist); - if (const auto rand = chance(dev); rand <= connectivity && rand >= dexp * scaling_connectivity) - connect(index1, index2); - } - } - - std::uniform_int_distribution node_select_dist(0ul, nodes.size() - 1); - for (blt::size_t i = 0; i < nodes.size(); i++) - { - if (connected_nodes[i].size() <= 1) - { - for (blt::size_t j = connected_nodes[i].size(); j < 2; j++) - { - blt::u64 select; - do - { - select = node_select_dist(dev); - if (select != i && !connected_nodes[i].contains(select)) - break; - } while (true); - connect(i, select); - } - } - } - } - - public: - graph_t() = default; - - 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, - const blt::f64 scaling_connectivity, const blt::f64 distance_factor) - { - sim = false; - current_iterations = 0; - max_force_last = 1.0; - nodes.clear(); - edges.clear(); - connected_nodes.clear(); - create_random_graph(bb, min_nodes, max_nodes, connectivity, scaling_connectivity, distance_factor); - } - - void connect(const blt::u64 n1, const blt::u64 n2) - { - edges.insert(edge{n1, n2}); - connected_nodes[n1].insert(n2); - connected_nodes[n2].insert(n1); - } - - [[nodiscard]] bool connected(blt::u64 e1, blt::u64 e2) const - { - return edges.contains({e1, e2}); - } - - void render(const double frame_time) - { - if (sim && (current_iterations < max_iterations || run_infinitely) && max_force_last > threshold) - { - for (int _ = 0; _ < sub_ticks; _++) - { - // calculate new forces - for (const auto& v1 : blt::enumerate(nodes)) - { - blt::vec2 attractive; - blt::vec2 repulsive; - for (const auto& v2 : blt::enumerate(nodes)) - { - if (v1.first == v2.first) - continue; - if (connected(v1.first, v2.first)) - attractive += equation->attr(v1, v2); - repulsive += equation->rep(v1, v2); - } - v1.second.getVelocityRef() = attractive + repulsive; - } - max_force_last = 0; - // update positions - for (auto& v : nodes) - { - const float sim_factor = static_cast(frame_time * sim_speed) * 0.05f; - v.getPositionRef() += v.getVelocityRef() * equation->cooling_factor(current_iterations) * sim_factor; - max_force_last = std::max(max_force_last, v.getVelocityRef().magnitude()); - } - current_iterations++; - } - } - - for (const auto& point : nodes) - renderer_2d.drawPointInternal(blt::gfx::render_info_t::make_info("parker_point", point.getOutlineColor(), point.getOutlineScale()), - point.getRenderObj(), 15.0f); - for (const auto& edge : edges) - { - if (edge.getFirst() >= nodes.size() || edge.getSecond() >= nodes.size()) - { - BLT_WARN("Edge Error %ld %ld %ld", edge.getFirst(), edge.getSecond(), nodes.size()); - } else - { - auto n1 = nodes[edge.getFirst()]; - auto n2 = nodes[edge.getSecond()]; - auto draw_info = blt::gfx::render_info_t::make_info(edge.getColor(), edge.getOutlineColor(), edge.getOutlineScale()); - renderer_2d.drawLine(draw_info, 5.0f, n1.getRenderObj().pos, n2.getRenderObj().pos, edge.getThickness()); - } - } - } - - void reset_mouse_drag() - { - current_node = -1; - } - - void 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())); - - if (current_node < 0) - { - for (const auto& [index, node] : blt::enumerate(nodes)) - { - const auto pos = node.getPosition(); - const auto dist = pos - mouse_pos; - - if (const auto mag = dist.magnitude(); mag < POINT_SIZE) - { - current_node = static_cast(index); - break; - } - } - } else - nodes[current_node].getPositionRef() = mouse_pos; - } - - void use_Eades() - { - equation = std::make_unique(); - } - - void use_Fruchterman_Reingold() - { - equation = std::make_unique(); - } - - void start_sim() - { - sim = true; - } - - void stop_sim() - { - sim = false; - } - - [[nodiscard]] std::string getSimulatorName() const - { - return equation->name(); - } - - [[nodiscard]] auto* getSimulator() const - { - return equation.get(); - } - - [[nodiscard]] auto getCoolingFactor() const - { - return equation->cooling_factor(current_iterations); - } - - void reset_iterations() - { - current_iterations = 0; - } - - [[nodiscard]] bool& getIterControl() - { - return run_infinitely; - } - - [[nodiscard]] float& getSimSpeed() - { - return sim_speed; - } - - [[nodiscard]] float& getThreshold() - { - return threshold; - } - - [[nodiscard]] int& getMaxIterations() - { - return max_iterations; - } - - [[nodiscard]] int numberOfNodes() const - { - return static_cast(nodes.size()); - } -}; - -class engine_t -{ - private: - graph_t graph; - - void draw_gui(const blt::gfx::window_data& data, const double ft) - { - if (im::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) - { - static int min_nodes = 5; - static int max_nodes = 25; - - static bounding_box bb{0, 0, data.width, data.height}; - - static float connectivity = 0.12; - static float scaling_connectivity = 0.5; - static float distance_factor = 100; - - //im::SetNextItemOpen(true, ImGuiCond_Once); - im::Text("FPS: %lf Frame-time (ms): %lf Frame-time (S): %lf", fps, ft * 1000.0, ft); - im::Text("Number of Nodes: %d", graph.numberOfNodes()); - im::SetNextItemOpen(true, ImGuiCond_Once); - if (im::CollapsingHeader("Help")) - { - im::Text("You can use W/A/S/D to move the camera around"); - im::Text("Q/E can be used to zoom in/out the camera"); - } - if (im::CollapsingHeader("Graph Generation Settings")) - { - im::Checkbox("Screen Auto-Scale", &bb.is_screen); - if (im::CollapsingHeader("Spawning Area")) - { - bool result = false; - result |= im::InputInt("Min X", &bb.min_x, 5, 100); - result |= im::InputInt("Max X", &bb.max_x, 5, 100); - result |= im::InputInt("Min Y", &bb.min_y, 5, 100); - result |= im::InputInt("Max Y", &bb.max_y, 5, 100); - if (result) - bb.is_screen = false; - } - if (bb.is_screen) - { - bb.max_x = data.width; - bb.max_y = data.height; - bb.min_x = 0; - bb.min_y = 0; - } - im::SeparatorText("Node Settings"); - im::InputInt("Min Nodes", &min_nodes); - im::InputInt("Max Nodes", &max_nodes); - im::SliderFloat("Connectivity", &connectivity, 0, 1); - im::SliderFloat("Scaling Connectivity", &scaling_connectivity, 0, 1); - im::InputFloat("Distance Factor", &distance_factor, 5, 100); - if (im::Button("Reset Graph")) - { - 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()); - im::Checkbox("Run Infinitely", &graph.getIterControl()); - im::InputInt("Sub-ticks Per Frame", &sub_ticks); - im::InputFloat("Threshold", &graph.getThreshold(), 0.01, 1); - graph.getSimulator()->draw_inputs_base(); - graph.getSimulator()->draw_inputs(); - im::Text("Current Cooling Factor: %f", graph.getCoolingFactor()); - im::SliderFloat("Simulation Speed", &graph.getSimSpeed(), 0, 4); - } - im::SetNextItemOpen(true, ImGuiCond_Once); - if (im::CollapsingHeader("System Controls")) - { - if (im::Button("Start")) - graph.start_sim(); - im::SameLine(); - if (im::Button("Stop")) - graph.stop_sim(); - if (im::Button("Reset Iterations")) - graph.reset_iterations(); - im::Text("Select a system:"); - const auto current_sim = graph.getSimulatorName(); - const char* items[] = {"Eades", "Fruchterman & Reingold"}; - static int item_current = 0; - ImGui::ListBox("##SillyBox", &item_current, items, 2, 2); - - if (strcmp(items[item_current], current_sim.c_str()) != 0) - { - switch (item_current) - { - case 0: - graph.use_Eades(); - BLT_INFO("Using Eades"); - break; - case 1: - graph.use_Fruchterman_Reingold(); - BLT_INFO("Using Fruchterman & Reingold"); - break; - default: - BLT_WARN("This is not a valid selection! How did we get here?"); - break; - } - } - } - im::End(); - } - } - - public: - void init(const blt::gfx::window_data& data) - { - graph.make_new({0, 0, data.width, data.height}, 5, 25, 0.2); - } - - void render(const blt::gfx::window_data& data, const double ft) - { - draw_gui(data, ft); - - auto& io = ImGui::GetIO(); - - if (!io.WantCaptureMouse && blt::gfx::isMousePressed(0)) - graph.process_mouse_drag(data.width, data.height); - else - graph.reset_mouse_drag(); - - - graph.render(ft); - } -}; - engine_t engine; void init(const blt::gfx::window_data& data) @@ -506,12 +80,11 @@ void update(const blt::gfx::window_data& data) const auto diff = currentTime - lastTime; lastTime = currentTime; ft = static_cast(diff) / 1000000000.0; - fps = 1 / ft; } int main(int, const char**) { - blt::gfx::init(blt::gfx::window_data{"Graphing Lovers United", init, update, 1440, 720}.setSyncInterval(1)); + blt::gfx::init(blt::gfx::window_data{"Force-Directed Graph Drawing", init, update, 1440, 720}.setSyncInterval(1)); global_matrices.cleanup(); resources.cleanup(); renderer_2d.cleanup();