-
Notifications
You must be signed in to change notification settings - Fork 385
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[skip ci] Introduce new
LayoutRequest
to get rid of Discriminator
- Loading branch information
Showing
8 changed files
with
226 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
use egui::{Pos2, Rect}; | ||
|
||
use crate::{graph::NodeIndex, ui::bounding_rect_from_iter}; | ||
|
||
pub type LineSegment = [Pos2; 2]; | ||
|
||
#[derive(Debug, PartialEq, Eq)] | ||
pub struct Layout { | ||
pub(super) nodes: ahash::HashMap<NodeIndex, Rect>, | ||
pub(super) edges: ahash::HashMap<(NodeIndex, NodeIndex), LineSegment>, | ||
} | ||
|
||
impl Layout { | ||
pub fn bounding_rect(&self) -> Rect { | ||
bounding_rect_from_iter(self.nodes.values().copied()) | ||
} | ||
|
||
/// Gets the final position and size of a node in the layout. | ||
pub fn get_node(&self, node: &NodeIndex) -> Option<Rect> { | ||
self.nodes.get(node).copied() | ||
} | ||
|
||
/// Gets the shape of an edge in the final layout. | ||
pub fn get_edge(&self, from: NodeIndex, to: NodeIndex) -> Option<LineSegment> { | ||
self.edges.get(&(from, to)).copied() | ||
} | ||
|
||
/// Updates the size and position of a node, for example after size changes. | ||
/// Returns `true` if the node changed its size. | ||
#[deprecated(note = "We should not need to update sizes anymore.")] | ||
pub fn update(&mut self, node: &NodeIndex, rect: Rect) -> bool { | ||
debug_assert!( | ||
self.nodes.contains_key(node), | ||
"node should exist in the layout" | ||
); | ||
if let Some(extent) = self.nodes.get_mut(node) { | ||
let size_changed = (extent.size() - rect.size()).length_sq() > 0.01; | ||
*extent = rect; | ||
return size_changed; | ||
} | ||
false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,127 +1,7 @@ | ||
use egui::{Pos2, Rect, Vec2}; | ||
use fjadra as fj; | ||
mod layout; | ||
mod provider; | ||
mod request; | ||
|
||
use crate::{ | ||
graph::{Graph, Node, NodeIndex}, | ||
ui::bounding_rect_from_iter, | ||
}; | ||
|
||
#[derive(Debug, PartialEq, Eq)] | ||
pub struct Layout { | ||
extents: ahash::HashMap<NodeIndex, Rect>, | ||
} | ||
|
||
impl Layout { | ||
pub fn bounding_rect(&self) -> Rect { | ||
bounding_rect_from_iter(self.extents.values().copied()) | ||
} | ||
|
||
/// Gets the position and size of a node in the layout. | ||
pub fn get(&self, node: &NodeIndex) -> Option<Rect> { | ||
self.extents.get(node).copied() | ||
} | ||
|
||
/// Updates the size and position of a node, for example after size changes. | ||
/// Returns `true` if the node changed its size. | ||
pub fn update(&mut self, node: &NodeIndex, rect: Rect) -> bool { | ||
debug_assert!( | ||
self.extents.contains_key(node), | ||
"node should exist in the layout" | ||
); | ||
if let Some(extent) = self.extents.get_mut(node) { | ||
let size_changed = (extent.size() - rect.size()).length_sq() > 0.01; | ||
*extent = rect; | ||
return size_changed; | ||
} | ||
false | ||
} | ||
} | ||
|
||
impl<'a> From<&'a Node> for fj::Node { | ||
fn from(node: &'a Node) -> Self { | ||
match node { | ||
Node::Explicit { | ||
position: Some(pos), | ||
.. | ||
} => Self::default().fixed_position(pos.x as f64, pos.y as f64), | ||
_ => Self::default(), | ||
} | ||
} | ||
} | ||
|
||
pub struct ForceLayout { | ||
simulation: fj::Simulation, | ||
node_index: ahash::HashMap<NodeIndex, usize>, | ||
} | ||
|
||
impl ForceLayout { | ||
pub fn new(graphs: &[Graph]) -> Self { | ||
let nodes = graphs | ||
.iter() | ||
.flat_map(|g| g.nodes().iter().map(|n| (n.id(), fj::Node::from(n)))); | ||
|
||
let mut node_index = ahash::HashMap::default(); | ||
let all_nodes: Vec<fj::Node> = nodes | ||
.enumerate() | ||
.map(|(i, n)| { | ||
node_index.insert(n.0, i); | ||
n.1 | ||
}) | ||
.collect(); | ||
|
||
let all_edges = graphs.iter().flat_map(|g| { | ||
g.edges() | ||
.iter() | ||
.map(|e| (node_index[&e.from], node_index[&e.to])) | ||
}); | ||
|
||
// TODO(grtlr): Currently we guesstimate good forces. Eventually these should be exposed as blueprints. | ||
let simulation = fj::SimulationBuilder::default() | ||
.with_alpha_decay(0.01) // TODO(grtlr): slows down the simulation for demo | ||
.build(all_nodes) | ||
.add_force( | ||
"link", | ||
fj::Link::new(all_edges).distance(50.0).iterations(2), | ||
) | ||
.add_force("charge", fj::ManyBody::new()) | ||
// TODO(grtlr): This is a small stop-gap until we have blueprints to prevent nodes from flying away. | ||
.add_force("x", fj::PositionX::new().strength(0.01)) | ||
.add_force("y", fj::PositionY::new().strength(0.01)); | ||
|
||
Self { | ||
simulation, | ||
node_index, | ||
} | ||
} | ||
|
||
pub fn init(&self) -> Layout { | ||
let positions = self.simulation.positions().collect::<Vec<_>>(); | ||
let mut extents = ahash::HashMap::default(); | ||
|
||
for (node, i) in &self.node_index { | ||
let [x, y] = positions[*i]; | ||
let pos = Pos2::new(x as f32, y as f32); | ||
let size = Vec2::ZERO; | ||
let rect = Rect::from_min_size(pos, size); | ||
extents.insert(*node, rect); | ||
} | ||
|
||
Layout { extents } | ||
} | ||
|
||
/// Returns `true` if finished. | ||
pub fn tick(&mut self, layout: &mut Layout) -> bool { | ||
self.simulation.tick(1); | ||
|
||
let positions = self.simulation.positions().collect::<Vec<_>>(); | ||
|
||
for (node, extent) in &mut layout.extents { | ||
let i = self.node_index[node]; | ||
let [x, y] = positions[i]; | ||
let pos = Pos2::new(x as f32, y as f32); | ||
extent.set_center(pos); | ||
} | ||
|
||
self.simulation.finished() | ||
} | ||
} | ||
pub use layout::Layout; | ||
pub use provider::ForceLayoutProvider; | ||
pub use request::LayoutRequest; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use egui::{Pos2, Rect, Vec2}; | ||
use fjadra as fj; | ||
|
||
use crate::graph::{Graph, Node, NodeIndex}; | ||
|
||
use super::Layout; | ||
|
||
impl<'a> From<&'a Node> for fj::Node { | ||
fn from(node: &'a Node) -> Self { | ||
match node { | ||
Node::Explicit { | ||
position: Some(pos), | ||
.. | ||
} => Self::default().fixed_position(pos.x as f64, pos.y as f64), | ||
_ => Self::default(), | ||
} | ||
} | ||
} | ||
|
||
pub struct ForceLayoutProvider { | ||
simulation: fj::Simulation, | ||
node_index: ahash::HashMap<NodeIndex, usize>, | ||
} | ||
|
||
impl ForceLayoutProvider { | ||
pub fn new(graphs: &[Graph]) -> Self { | ||
let nodes = graphs | ||
.iter() | ||
.flat_map(|g| g.nodes().iter().map(|n| (n.id(), fj::Node::from(n)))); | ||
|
||
let mut node_index = ahash::HashMap::default(); | ||
let all_nodes: Vec<fj::Node> = nodes | ||
.enumerate() | ||
.map(|(i, n)| { | ||
node_index.insert(n.0, i); | ||
n.1 | ||
}) | ||
.collect(); | ||
|
||
let all_edges = graphs.iter().flat_map(|g| { | ||
g.edges() | ||
.iter() | ||
.map(|e| (node_index[&e.from], node_index[&e.to])) | ||
}); | ||
|
||
// TODO(grtlr): Currently we guesstimate good forces. Eventually these should be exposed as blueprints. | ||
let simulation = fj::SimulationBuilder::default() | ||
.with_alpha_decay(0.01) // TODO(grtlr): slows down the simulation for demo | ||
.build(all_nodes) | ||
.add_force( | ||
"link", | ||
fj::Link::new(all_edges).distance(50.0).iterations(2), | ||
) | ||
.add_force("charge", fj::ManyBody::new()) | ||
// TODO(grtlr): This is a small stop-gap until we have blueprints to prevent nodes from flying away. | ||
.add_force("x", fj::PositionX::new().strength(0.01)) | ||
.add_force("y", fj::PositionY::new().strength(0.01)); | ||
|
||
Self { | ||
simulation, | ||
node_index, | ||
} | ||
} | ||
|
||
pub fn init(&self) -> Layout { | ||
let positions = self.simulation.positions().collect::<Vec<_>>(); | ||
let mut extents = ahash::HashMap::default(); | ||
|
||
for (node, i) in &self.node_index { | ||
let [x, y] = positions[*i]; | ||
let pos = Pos2::new(x as f32, y as f32); | ||
let size = Vec2::ZERO; | ||
let rect = Rect::from_min_size(pos, size); | ||
extents.insert(*node, rect); | ||
} | ||
|
||
Layout { nodes: extents, edges: ahash::HashMap::default() } | ||
} | ||
|
||
/// Returns `true` if finished. | ||
pub fn tick(&mut self, layout: &mut Layout) -> bool { | ||
self.simulation.tick(1); | ||
|
||
let positions = self.simulation.positions().collect::<Vec<_>>(); | ||
|
||
for (node, extent) in &mut layout.nodes { | ||
let i = self.node_index[node]; | ||
let [x, y] = positions[i]; | ||
let pos = Pos2::new(x as f32, y as f32); | ||
extent.set_center(pos); | ||
} | ||
|
||
self.simulation.finished() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use std::collections::{BTreeMap, BTreeSet}; | ||
|
||
use egui::{Pos2, Vec2}; | ||
use re_chunk::EntityPath; | ||
|
||
use crate::graph::{Graph, NodeIndex}; | ||
|
||
#[derive(PartialEq)] | ||
struct NodeTemplate { | ||
size: Vec2, | ||
fixed_position: Option<Pos2>, | ||
} | ||
|
||
#[derive(Default, PartialEq)] | ||
struct GraphTemplate { | ||
nodes: BTreeMap<NodeIndex, NodeTemplate>, | ||
edges: BTreeSet<(NodeIndex, NodeIndex)>, | ||
} | ||
|
||
#[derive(PartialEq)] | ||
pub struct LayoutRequest { | ||
graphs: BTreeMap<EntityPath, GraphTemplate>, | ||
} | ||
|
||
impl LayoutRequest { | ||
pub fn from_graphs<'a>(graphs: impl IntoIterator<Item = &'a Graph>) -> Self { | ||
let mut request = Self { | ||
graphs: BTreeMap::new(), | ||
}; | ||
|
||
for graph in graphs { | ||
let entity = request.graphs.entry(graph.entity().clone()).or_default(); | ||
|
||
for node in graph.nodes() { | ||
let shape = NodeTemplate { | ||
size: node.size(), | ||
fixed_position: node.position(), | ||
}; | ||
entity.nodes.insert(node.id(), shape); | ||
} | ||
|
||
for edge in graph.edges() { | ||
entity.edges.insert((edge.from, edge.to)); | ||
} | ||
} | ||
|
||
request | ||
} | ||
} |
Oops, something went wrong.