hey you, added the other function
parent
40bb9302ea
commit
7173112bd5
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.25)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
project(graphs VERSION 0.0.18)
|
project(graphs VERSION 0.0.19)
|
||||||
|
|
||||||
option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF)
|
option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF)
|
||||||
option(ENABLE_UBSAN "Enable the ub sanitizer" OFF)
|
option(ENABLE_UBSAN "Enable the ub sanitizer" OFF)
|
||||||
|
|
287
src/main.cpp
287
src/main.cpp
|
@ -104,6 +104,141 @@ struct edge_hash
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct equation_variables
|
||||||
|
{
|
||||||
|
float repulsive_constant = 12.0;
|
||||||
|
float spring_constant = 24.0;
|
||||||
|
float ideal_spring_length = 175.0;
|
||||||
|
float initial_temperature = 69.5;
|
||||||
|
float cooling_rate = 0.999;
|
||||||
|
};
|
||||||
|
|
||||||
|
class force_equation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using node_pair = const std::pair<blt::size_t, node>&;
|
||||||
|
protected:
|
||||||
|
const equation_variables variables;
|
||||||
|
|
||||||
|
struct equation_data
|
||||||
|
{
|
||||||
|
blt::vec2 unit;
|
||||||
|
float mag, mag_sq;
|
||||||
|
|
||||||
|
equation_data(blt::vec2 unit, float mag, float mag_sq): unit(unit), mag(mag), mag_sq(mag_sq)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline static blt::vec2 dir_v(node_pair v1, node_pair v2)
|
||||||
|
{
|
||||||
|
return v2.second.getPosition() - v1.second.getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static equation_data calc_data(node_pair v1, node_pair v2)
|
||||||
|
{
|
||||||
|
auto dir = dir_v(v1, v2);
|
||||||
|
auto mag = dir.magnitude();
|
||||||
|
auto unit = mag == 0 ? blt::vec2() : dir / mag;
|
||||||
|
auto mag_sq = mag * mag;
|
||||||
|
return {unit, mag, mag_sq};
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit force_equation(const equation_variables& variables): variables(variables)
|
||||||
|
{}
|
||||||
|
|
||||||
|
[[nodiscard]] virtual blt::vec2 attr(node_pair v1, node_pair v2) const = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual blt::vec2 rep(node_pair v1, node_pair v2) const = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual std::string name() const = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual float cooling_factor(int t) const
|
||||||
|
{
|
||||||
|
return static_cast<float>(variables.initial_temperature * std::pow(variables.cooling_rate, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~force_equation() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Eades_equation : public force_equation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Eades_equation(const equation_variables& variables): force_equation(variables)
|
||||||
|
{}
|
||||||
|
|
||||||
|
[[nodiscard]] blt::vec2 attr(node_pair v1, node_pair v2) const final
|
||||||
|
{
|
||||||
|
auto data = calc_data(v1, v2);
|
||||||
|
|
||||||
|
auto ideal = std::log(data.mag / variables.ideal_spring_length);
|
||||||
|
|
||||||
|
return variables.spring_constant * ideal * data.unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] blt::vec2 rep(node_pair v1, node_pair v2) const final
|
||||||
|
{
|
||||||
|
auto data = calc_data(v1, v2);
|
||||||
|
|
||||||
|
auto scale = variables.repulsive_constant / data.mag_sq;
|
||||||
|
return scale * -data.unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string name() const final
|
||||||
|
{
|
||||||
|
return "Eades";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Fruchterman_Reingold_equation : public force_equation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Fruchterman_Reingold_equation(const equation_variables& variables): force_equation(variables)
|
||||||
|
{}
|
||||||
|
|
||||||
|
[[nodiscard]] blt::vec2 attr(node_pair v1, node_pair v2) const final
|
||||||
|
{
|
||||||
|
auto data = calc_data(v1, v2);
|
||||||
|
|
||||||
|
float scale = data.mag_sq / variables.ideal_spring_length;
|
||||||
|
|
||||||
|
return scale * data.unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] blt::vec2 rep(node_pair v1, node_pair v2) const final
|
||||||
|
{
|
||||||
|
auto data = calc_data(v1, v2);
|
||||||
|
|
||||||
|
float scale = (variables.ideal_spring_length * variables.ideal_spring_length) / data.mag;
|
||||||
|
|
||||||
|
return scale * -data.unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] float cooling_factor(int t) const override
|
||||||
|
{
|
||||||
|
return force_equation::cooling_factor(t) * 0.025f;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string name() const final
|
||||||
|
{
|
||||||
|
return "Fruchterman & Reingold";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bounding_box
|
||||||
|
{
|
||||||
|
int min_x = 0;
|
||||||
|
int min_y = 0;
|
||||||
|
int max_x = 0;
|
||||||
|
int max_y = 0;
|
||||||
|
|
||||||
|
bounding_box(int min_x, int min_y, int max_x, 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
|
class graph
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -114,52 +249,27 @@ class graph
|
||||||
float sim_speed = 1;
|
float sim_speed = 1;
|
||||||
float threshold = 0.01;
|
float threshold = 0.01;
|
||||||
float max_force_last = 1;
|
float max_force_last = 1;
|
||||||
float repulsive_constant = 12.0;
|
|
||||||
float spring_constant = 24.0;
|
|
||||||
float ideal_spring_length = 175.0;
|
|
||||||
float initial_temperature = 69.5;
|
|
||||||
float cooling_rate = 0.999;
|
|
||||||
int current_iterations = 0;
|
int current_iterations = 0;
|
||||||
int max_iterations = 5000;
|
int max_iterations = 5000;
|
||||||
|
equation_variables variables;
|
||||||
|
std::unique_ptr<force_equation> equation;
|
||||||
static constexpr float POINT_SIZE = 35;
|
static constexpr float POINT_SIZE = 35;
|
||||||
|
|
||||||
[[nodiscard]] blt::vec2 attr(const std::pair<blt::size_t, node>& v1, const std::pair<blt::size_t, node>& v2) const
|
void create_random_graph(bounding_box bb, blt::size_t min_nodes, blt::size_t max_nodes, blt::f64 connectivity)
|
||||||
{
|
|
||||||
auto dir = v2.second.getPosition() - v1.second.getPosition();
|
|
||||||
auto mag = dir.magnitude();
|
|
||||||
auto unit = mag == 0 ? blt::vec2() : dir / mag;
|
|
||||||
|
|
||||||
auto ideal = std::log(mag / ideal_spring_length);
|
|
||||||
|
|
||||||
return spring_constant * ideal * unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] blt::vec2 rep(const std::pair<blt::size_t, node>& v1, const std::pair<blt::size_t, node>& v2) const
|
|
||||||
{
|
|
||||||
auto dir = v2.second.getPosition() - v1.second.getPosition();
|
|
||||||
auto mag = dir.magnitude();
|
|
||||||
auto unit = mag == 0 ? blt::vec2() : dir / mag;
|
|
||||||
auto mag_sq = mag * mag;
|
|
||||||
|
|
||||||
auto scale = repulsive_constant / mag_sq;
|
|
||||||
return scale * unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] float cooling_factor() const
|
|
||||||
{
|
|
||||||
return static_cast<float>(initial_temperature * std::pow(cooling_rate, current_iterations));
|
|
||||||
}
|
|
||||||
|
|
||||||
void create_random_graph(blt::i32 width, blt::i32 height, blt::size_t min_nodes, blt::size_t max_nodes, blt::f64 connectivity)
|
|
||||||
{
|
{
|
||||||
// don't allow points too close to the edges of the window.
|
// don't allow points too close to the edges of the window.
|
||||||
width -= POINT_SIZE;
|
if (bb.is_screen)
|
||||||
height -= POINT_SIZE;
|
{
|
||||||
|
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::random_device dev;
|
||||||
static std::uniform_real_distribution chance(0.0, 1.0);
|
static std::uniform_real_distribution chance(0.0, 1.0);
|
||||||
std::uniform_int_distribution node_count_dist(min_nodes, max_nodes);
|
std::uniform_int_distribution node_count_dist(min_nodes, max_nodes);
|
||||||
std::uniform_real_distribution pos_x_dist(POINT_SIZE, static_cast<blt::f32>(width));
|
std::uniform_real_distribution pos_x_dist(static_cast<blt::f32>(bb.min_x), static_cast<blt::f32>(bb.max_x));
|
||||||
std::uniform_real_distribution pos_y_dist(POINT_SIZE, static_cast<blt::f32>(height));
|
std::uniform_real_distribution pos_y_dist(static_cast<blt::f32>(bb.min_y), static_cast<blt::f32>(bb.max_y));
|
||||||
|
|
||||||
auto node_count = node_count_dist(dev);
|
auto node_count = node_count_dist(dev);
|
||||||
|
|
||||||
|
@ -224,12 +334,13 @@ class graph
|
||||||
public:
|
public:
|
||||||
graph() = default;
|
graph() = default;
|
||||||
|
|
||||||
graph(blt::i32 width, blt::i32 height, blt::size_t min_nodes, blt::size_t max_nodes, blt::f64 connectivity)
|
graph(const bounding_box& bb, blt::size_t min_nodes, blt::size_t max_nodes, blt::f64 connectivity)
|
||||||
{
|
{
|
||||||
create_random_graph(width, height, min_nodes, max_nodes, connectivity);
|
create_random_graph(bb, min_nodes, max_nodes, connectivity);
|
||||||
|
use_Eades();
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset(blt::i32 width, blt::i32 height, blt::size_t min_nodes, blt::size_t max_nodes, blt::f64 connectivity)
|
void reset(const bounding_box& bb, blt::size_t min_nodes, blt::size_t max_nodes, blt::f64 connectivity)
|
||||||
{
|
{
|
||||||
sim = false;
|
sim = false;
|
||||||
current_iterations = 0;
|
current_iterations = 0;
|
||||||
|
@ -237,7 +348,7 @@ class graph
|
||||||
nodes.clear();
|
nodes.clear();
|
||||||
edges.clear();
|
edges.clear();
|
||||||
connected_nodes.clear();
|
connected_nodes.clear();
|
||||||
create_random_graph(width, height, min_nodes, max_nodes, connectivity);
|
create_random_graph(bb, min_nodes, max_nodes, connectivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void connect(blt::u64 n1, blt::u64 n2)
|
void connect(blt::u64 n1, blt::u64 n2)
|
||||||
|
@ -267,8 +378,8 @@ class graph
|
||||||
{
|
{
|
||||||
if (v1.first == v2.first)
|
if (v1.first == v2.first)
|
||||||
continue;
|
continue;
|
||||||
attractive += attr(v1, v2);
|
attractive += equation->attr(v1, v2);
|
||||||
repulsive += rep(v1, v2);
|
repulsive += equation->rep(v1, v2);
|
||||||
}
|
}
|
||||||
v1.second.getVelocityRef() = attractive + repulsive;
|
v1.second.getVelocityRef() = attractive + repulsive;
|
||||||
}
|
}
|
||||||
|
@ -276,7 +387,8 @@ class graph
|
||||||
// update positions
|
// update positions
|
||||||
for (auto& v : nodes)
|
for (auto& v : nodes)
|
||||||
{
|
{
|
||||||
v.getPositionRef() += v.getVelocityRef() * cooling_factor() * static_cast<float>(frame_time * sim_speed) * 0.05f;
|
float sim_factor = static_cast<float>(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());
|
max_force_last = std::max(max_force_last, v.getVelocityRef().magnitude());
|
||||||
}
|
}
|
||||||
current_iterations++;
|
current_iterations++;
|
||||||
|
@ -299,6 +411,16 @@ class graph
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void use_Eades()
|
||||||
|
{
|
||||||
|
equation = std::make_unique<Eades_equation>(variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
void use_Fruchterman_Reingold()
|
||||||
|
{
|
||||||
|
equation = std::make_unique<Fruchterman_Reingold_equation>(variables);
|
||||||
|
}
|
||||||
|
|
||||||
void start_sim()
|
void start_sim()
|
||||||
{
|
{
|
||||||
sim = true;
|
sim = true;
|
||||||
|
@ -309,6 +431,11 @@ class graph
|
||||||
sim = false;
|
sim = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string getSimulatorName()
|
||||||
|
{
|
||||||
|
return equation->name();
|
||||||
|
}
|
||||||
|
|
||||||
float& getSimSpeed()
|
float& getSimSpeed()
|
||||||
{
|
{
|
||||||
return sim_speed;
|
return sim_speed;
|
||||||
|
@ -321,27 +448,27 @@ class graph
|
||||||
|
|
||||||
float& getSpringConstant()
|
float& getSpringConstant()
|
||||||
{
|
{
|
||||||
return spring_constant;
|
return variables.spring_constant;
|
||||||
}
|
}
|
||||||
|
|
||||||
float& getInitialTemperature()
|
float& getInitialTemperature()
|
||||||
{
|
{
|
||||||
return initial_temperature;
|
return variables.initial_temperature;
|
||||||
}
|
}
|
||||||
|
|
||||||
float& getCoolingRate()
|
float& getCoolingRate()
|
||||||
{
|
{
|
||||||
return cooling_rate;
|
return variables.cooling_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
float& getIdealSpringLength()
|
float& getIdealSpringLength()
|
||||||
{
|
{
|
||||||
return ideal_spring_length;
|
return variables.ideal_spring_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
float& getRepulsionConstant()
|
float& getRepulsionConstant()
|
||||||
{
|
{
|
||||||
return repulsive_constant;
|
return variables.repulsive_constant;
|
||||||
}
|
}
|
||||||
|
|
||||||
int& getMaxIterations()
|
int& getMaxIterations()
|
||||||
|
@ -376,7 +503,8 @@ void init(const blt::gfx::window_data& data)
|
||||||
resources.load_resources();
|
resources.load_resources();
|
||||||
renderer_2d.create();
|
renderer_2d.create();
|
||||||
|
|
||||||
main_graph = graph(data.width, data.height, 5, 25, 0.2);
|
bounding_box bb(0, 0, data.width, data.height);
|
||||||
|
main_graph = graph(bb, 5, 25, 0.2);
|
||||||
lastTime = blt::system::nanoTime();
|
lastTime = blt::system::nanoTime();
|
||||||
|
|
||||||
//render_texture = fbo_t::make_multisample_render_texture(1440, 720, 4);
|
//render_texture = fbo_t::make_multisample_render_texture(1440, 720, 4);
|
||||||
|
@ -409,19 +537,48 @@ void update(const blt::gfx::window_data& data)
|
||||||
{
|
{
|
||||||
static int min_nodes = 5;
|
static int min_nodes = 5;
|
||||||
static int max_nodes = 25;
|
static int max_nodes = 25;
|
||||||
|
|
||||||
|
static bounding_box bb {0, 0, data.width, data.height};
|
||||||
|
|
||||||
static float connectivity = 0.12;
|
static float connectivity = 0.12;
|
||||||
|
|
||||||
//im::SetNextItemOpen(true, ImGuiCond_Once);
|
//im::SetNextItemOpen(true, ImGuiCond_Once);
|
||||||
im::Text("FPS: %lf Frame-time (ms): %lf Frame-time (S): %lf", fps, ft * 1000.0, ft);
|
im::Text("FPS: %lf Frame-time (ms): %lf Frame-time (S): %lf", fps, ft * 1000.0, ft);
|
||||||
im::Text("Number of Nodes: %d", main_graph.numberOfNodes());
|
im::Text("Number of Nodes: %d", main_graph.numberOfNodes());
|
||||||
|
im::SetNextItemOpen(true, ImGuiCond_Once);
|
||||||
|
if (im::CollapsingHeader("Help"))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
if (im::CollapsingHeader("Graph Generation Settings"))
|
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("Min Nodes", &min_nodes);
|
||||||
im::InputInt("Max Nodes", &max_nodes);
|
im::InputInt("Max Nodes", &max_nodes);
|
||||||
im::SliderFloat("Connectivity", &connectivity, 0, 1);
|
im::SliderFloat("Connectivity", &connectivity, 0, 1);
|
||||||
if (im::Button("Reset Graph"))
|
if (im::Button("Reset Graph"))
|
||||||
{
|
{
|
||||||
main_graph.reset(data.width, data.height, min_nodes, max_nodes, connectivity);
|
main_graph.reset(bb, min_nodes, max_nodes, connectivity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
im::SetNextItemOpen(true, ImGuiCond_Once);
|
im::SetNextItemOpen(true, ImGuiCond_Once);
|
||||||
|
@ -442,6 +599,34 @@ void update(const blt::gfx::window_data& data)
|
||||||
if (im::Button("Stop"))
|
if (im::Button("Stop"))
|
||||||
main_graph.stop_sim();
|
main_graph.stop_sim();
|
||||||
}
|
}
|
||||||
|
im::SetNextItemOpen(true, ImGuiCond_Once);
|
||||||
|
if (im::CollapsingHeader("System Controls"))
|
||||||
|
{
|
||||||
|
//im::Text("Current System: %s", main_graph.getSimulatorName().c_str());
|
||||||
|
//im::
|
||||||
|
auto current_sim = main_graph.getSimulatorName();
|
||||||
|
const char* items[] = {"Eades", "Fruchterman & Reingold"};
|
||||||
|
static int item_current = 0;
|
||||||
|
ImGui::ListBox("", &item_current, items, IM_ARRAYSIZE(items), 4);
|
||||||
|
|
||||||
|
if (strcmp(items[item_current], current_sim.c_str()) != 0)
|
||||||
|
{
|
||||||
|
switch (item_current)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
main_graph.use_Eades();
|
||||||
|
BLT_INFO("Using Eades");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
main_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();
|
im::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue