use petgraph::{
stable_graph::{NodeIndex, StableUnGraph},
visit::{EdgeRef, IntoEdgeReferences},
};
use std::collections::BTreeSet;
pub type DefaultNodeIdx = NodeIndex<petgraph::stable_graph::DefaultIx>;
#[derive(Clone, Debug)]
pub struct SimulationParameters {
pub force_charge: f32,
pub force_spring: f32,
pub force_max: f32,
pub node_speed: f32,
pub damping_factor: f32,
}
impl Default for SimulationParameters {
fn default() -> Self {
SimulationParameters {
force_charge: 12000.0,
force_spring: 0.3,
force_max: 280.0,
node_speed: 7000.0,
damping_factor: 0.95,
}
}
}
pub struct NodeData<UserNodeData = ()> {
pub x: f32,
pub y: f32,
pub mass: f32,
pub is_anchor: bool,
pub user_data: UserNodeData,
}
impl<UserNodeData> Default for NodeData<UserNodeData>
where
UserNodeData: Default,
{
fn default() -> Self {
NodeData {
x: 0.0,
y: 0.0,
mass: 10.0,
is_anchor: false,
user_data: Default::default(),
}
}
}
pub struct EdgeData<UserEdgeData = ()> {
pub user_data: UserEdgeData,
}
impl<UserEdgeData> Default for EdgeData<UserEdgeData>
where
UserEdgeData: Default,
{
fn default() -> Self {
EdgeData {
user_data: Default::default(),
}
}
}
pub struct ForceGraph<UserNodeData = (), UserEdgeData = ()> {
pub parameters: SimulationParameters,
graph: StableUnGraph<Node<UserNodeData>, EdgeData<UserEdgeData>>,
node_indices: BTreeSet<DefaultNodeIdx>,
}
impl<UserNodeData, UserEdgeData> ForceGraph<UserNodeData, UserEdgeData> {
pub fn new(parameters: SimulationParameters) -> Self {
ForceGraph {
parameters,
graph: StableUnGraph::default(),
node_indices: Default::default(),
}
}
pub fn get_graph(&self) -> &StableUnGraph<Node<UserNodeData>, EdgeData<UserEdgeData>> {
&self.graph
}
pub fn add_node(&mut self, node_data: NodeData<UserNodeData>) -> DefaultNodeIdx {
let idx = self.graph.add_node(Node {
data: node_data,
index: Default::default(),
vx: 0.0,
vy: 0.0,
ax: 0.0,
ay: 0.0,
});
self.graph[idx].index = idx;
self.node_indices.insert(idx);
idx
}
pub fn remove_node(&mut self, idx: DefaultNodeIdx) {
self.graph.remove_node(idx);
self.node_indices.remove(&idx);
}
pub fn add_edge(
&mut self,
n1_idx: DefaultNodeIdx,
n2_idx: DefaultNodeIdx,
edge: EdgeData<UserEdgeData>,
) {
self.graph.update_edge(n1_idx, n2_idx, edge);
}
pub fn clear(&mut self) {
self.graph.clear();
self.node_indices.clear();
}
pub fn update(&mut self, dt: f32) {
if self.graph.node_count() == 0 {
return;
}
for (n1_idx_i, n1_idx) in self.node_indices.iter().enumerate() {
let mut edges = self.graph.neighbors(*n1_idx).detach();
while let Some(n2_idx) = edges.next_node(&self.graph) {
let (n1, n2) = self.graph.index_twice_mut(*n1_idx, n2_idx);
let f = attract_nodes(n1, n2, &self.parameters);
n1.apply_force(f.0, f.1, dt, &self.parameters);
}
for n2_idx in self.node_indices.iter().skip(n1_idx_i + 1) {
let (n1, n2) = self.graph.index_twice_mut(*n1_idx, *n2_idx);
let f = repel_nodes(n1, n2, &self.parameters);
if !n1.data.is_anchor {
n1.apply_force(f.0, f.1, dt, &self.parameters);
}
if !n2.data.is_anchor {
n2.apply_force(-f.0, -f.1, dt, &self.parameters);
}
}
let n1 = &mut self.graph[*n1_idx];
if !n1.data.is_anchor {
n1.update(dt, &self.parameters);
}
}
}
pub fn visit_nodes<F: FnMut(&Node<UserNodeData>)>(&self, mut cb: F) {
for n_idx in self.graph.node_indices() {
cb(&self.graph[n_idx]);
}
}
pub fn visit_nodes_mut<F: FnMut(&mut Node<UserNodeData>)>(&mut self, mut cb: F) {
for node in self.graph.node_weights_mut() {
cb(node);
}
}
pub fn visit_edges<
F: FnMut(&Node<UserNodeData>, &Node<UserNodeData>, &EdgeData<UserEdgeData>),
>(
&self,
mut cb: F,
) {
for edge_ref in self.graph.edge_references() {
let source = &self.graph[edge_ref.source()];
let target = &self.graph[edge_ref.target()];
let edge_data = edge_ref.weight();
cb(source, target, edge_data);
}
}
}
pub struct Node<UserNodeData = ()> {
pub data: NodeData<UserNodeData>,
index: DefaultNodeIdx,
vx: f32,
vy: f32,
ax: f32,
ay: f32,
}
impl<UserNodeData> Node<UserNodeData> {
pub fn x(&self) -> f32 {
self.data.x
}
pub fn y(&self) -> f32 {
self.data.y
}
pub fn index(&self) -> DefaultNodeIdx {
self.index
}
fn apply_force(&mut self, fx: f32, fy: f32, dt: f32, parameters: &SimulationParameters) {
self.ax += fx.max(-parameters.force_max).min(parameters.force_max) * dt;
self.ay += fy.max(-parameters.force_max).min(parameters.force_max) * dt;
}
fn update(&mut self, dt: f32, parameters: &SimulationParameters) {
self.vx = (self.vx + self.ax * dt * parameters.node_speed) * parameters.damping_factor;
self.vy = (self.vy + self.ay * dt * parameters.node_speed) * parameters.damping_factor;
self.data.x += self.vx * dt;
self.data.y += self.vy * dt;
self.ax = 0.0;
self.ay = 0.0;
}
}
fn attract_nodes<D>(n1: &Node<D>, n2: &Node<D>, parameters: &SimulationParameters) -> (f32, f32) {
let mut dx = n2.data.x - n1.data.x;
let mut dy = n2.data.y - n1.data.y;
let distance = if dx == 0.0 && dy == 0.0 {
1.0
} else {
(dx * dx + dy * dy).sqrt()
};
dx /= distance;
dy /= distance;
let strength = 1.0 * parameters.force_spring * distance * 0.5;
(dx * strength, dy * strength)
}
fn repel_nodes<D>(n1: &Node<D>, n2: &Node<D>, parameters: &SimulationParameters) -> (f32, f32) {
let mut dx = n2.data.x - n1.data.x;
let mut dy = n2.data.y - n1.data.y;
let distance = if dx == 0.0 && dy == 0.0 {
1.0
} else {
(dx * dx + dy * dy).sqrt()
};
dx /= distance;
dy /= distance;
let distance_sqrd = distance * distance;
let strength = -parameters.force_charge * ((n1.data.mass * n2.data.mass) / distance_sqrd);
(dx * strength, dy * strength)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_default() {
let mut graph = <ForceGraph>::new(Default::default());
let n1_idx = graph.add_node(NodeData {
x: 0.1,
y: 0.2,
..Default::default()
});
let n2_idx = graph.add_node(NodeData {
x: 0.3,
y: 0.4,
..Default::default()
});
graph.add_edge(n1_idx, n2_idx, Default::default());
}
#[test]
fn test_user_data() {
let mut graph = ForceGraph::new(Default::default());
#[derive(Default)]
struct UserNodeData {}
#[derive(Default)]
struct UserEdgeData {}
let n1_idx = graph.add_node(NodeData {
x: 0.1,
y: 0.2,
user_data: UserNodeData {},
..Default::default()
});
let n2_idx = graph.add_node(NodeData {
x: 0.3,
y: 0.4,
user_data: UserNodeData {},
..Default::default()
});
graph.add_edge(
n1_idx,
n2_idx,
EdgeData {
user_data: UserEdgeData {},
},
);
}
}