diff --git a/src/components/background.rs b/src/components/background.rs index 28b0fea..fb56834 100644 --- a/src/components/background.rs +++ b/src/components/background.rs @@ -18,7 +18,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { let height = canvas_parent.client_height(); canvas.set_width(u32::try_from(width).unwrap()); canvas.set_height(u32::try_from(height).unwrap()); - let sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap()); + let mut sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap()); // TODO Resize on window resize log!( "TODO resize on window resize canvas parent size = {} {}", @@ -38,10 +38,6 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { context.set_fill_style(&JsValue::from("yellow")); log!("About to render nodes"); let start_time = window().unwrap().performance().unwrap().now(); - for n in sc.nodes_tree.borrow().iter() { - context.fill_rect(n.position.x.into(), n.position.y.into(), 5.0, 5.0); - } - context.begin_path(); let render_node_fn = |n: &Node, child: &Node| { context.move_to(n.position.x.into(), n.position.y.into()); @@ -52,14 +48,13 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { context.stroke(); context.set_fill_style(&JsValue::from("magenta")); - for a in sc.attractors.borrow().iter() { + for a in sc.attractors.iter() { context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0); } let end_time = window().unwrap().performance().unwrap().now(); log!( - "Rendering {} nodes and {} attractors took {}", - sc.nodes_tree.borrow().len(), - sc.attractors.borrow().len(), + "Rendering nodes and {} attractors took {}", + sc.attractors.len(), end_time - start_time ); diff --git a/src/space_colonization/mod.rs b/src/space_colonization/mod.rs index 1447000..b84f929 100644 --- a/src/space_colonization/mod.rs +++ b/src/space_colonization/mod.rs @@ -12,7 +12,7 @@ extern "C" { fn performance() -> web_sys::Performance; } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Hash)] pub struct Attractor { pub position: Point, pub dead: bool, @@ -38,6 +38,9 @@ impl Attractor { /// - Probably worth marking nodes as dead when no attractor is in range #[derive(Debug, PartialEq, Eq)] pub struct Node { + /// When a Node is born it is growing + /// it stops growing when there is no attractor in range + pub growing: bool, pub position: Point, pub children: Vec, } @@ -76,6 +79,7 @@ impl Node { fn new(position: Point) -> Self { Self { position, + growing: true, children: Vec::new().into(), } } diff --git a/src/space_colonization/space_colonization.rs b/src/space_colonization/space_colonization.rs index bb0babc..718850f 100644 --- a/src/space_colonization/space_colonization.rs +++ b/src/space_colonization/space_colonization.rs @@ -1,5 +1,4 @@ -use super::math::calculate_new_node_position; -use super::{Attractor, Node, NodeRef, Point}; +use super::{Attractor, Node, Point}; use log::info; use rand::thread_rng; use rand::Rng; @@ -40,9 +39,21 @@ pub struct SpaceColonization { /// /// If density is 10, then there will be an average distance of 10 between attractors density: i32, - new_nodes: Vec<(NodeRef, Node)>, - /// Flat list of all nodes in the tree - /// [node, child1, grand-child, child2, grand-child2] + /// Tree of all nodes in the space. There can be multiple root nodes. + /// ```yaml + /// [ + /// node: { + /// position: (x,y), + /// children : [ + /// child1 : { + /// position, children: [ grand-child1, grand-child2 ] + /// }, + /// child2 : {...} + /// ], + /// }, + /// node2: { ...another tree }, + /// ] + /// ``` nodes: Vec, pub attractors: Vec, } @@ -50,10 +61,7 @@ pub struct SpaceColonization { impl SpaceColonization { pub fn new(width: i32, height: i32) -> SpaceColonization { let mut nodes_vec = Vec::new(); - nodes_vec.push(Node { - position: Point { x: 100, y: 100 }, - children: Vec::new(), - }); + nodes_vec.push(Node::new(Point::new((100, 100)))); let attractors = Vec::new(); let mut sc = SpaceColonization { @@ -67,7 +75,6 @@ impl SpaceColonization { density: 30, nodes: nodes_vec, attractors, - new_nodes: Vec::new(), }; sc.place_attractors(); @@ -93,7 +100,6 @@ impl SpaceColonization { density: 3, nodes, attractors, - new_nodes: Vec::new(), } } @@ -147,78 +153,87 @@ impl SpaceColonization { } } - pub fn grow(&self) { + pub fn grow(&mut self) { // TODO - // Find a clean API that will be stable across refactoring - // Write the test against this api including performance - // Find a way to make a compile-time safe datastructure that will ensure that + // [x] Find a clean API that will be stable across refactoring + // [ ] Write the test against this api including performance + // [x] Find a way to make a compile-time safe datastructure that will ensure that // - I can store my attractors and their state (remove them or update them when dead) // - I can store my nodes and their state // - I can efficiently render my nodes on a canvas // - I use as little memory as possible // - I can update my nodes self.grow_nodes(); - println!("new nodes for iteration {:?}", self.new_nodes); - let mut nodes_mut = self.nodes; - for new_pair in self.new_nodes.iter() { - new_pair.0.children.push(new_pair.1); - nodes_mut.push(new_pair.1); - } - self.new_nodes.clear(); } - pub fn grow_nodes(&self) { + pub fn grow_nodes(&mut self) { // iterate through attractors // find closest node within attraction range // build a map of nodes to affecting attractors // attractors within the attraction range that this node is the closest to // // calculate new node position - let attractors = self.attractors; - let mut growing_paths: HashMap<, Vec> = HashMap::new(); - for a in attractors.iter() { - if a.dead { - continue; - } - let mut closest_node: Option = None; - let mut closest_node_distance = f64::MAX; - - for n in self.nodes.iter() { - let distance = n.position.distance(&a.position); - if distance <= self.attraction_distance as f64 { - // TODO make sure it is closest node amongs all nodes - if distance < closest_node_distance { - closest_node = Some(n); - closest_node_distance = distance; - if distance < self.kill_distance as f64 { - a.dead.replace(true); - } - } - } - } - if let Some(node) = closest_node { - if let Some(attractors) = growing_paths.get_mut(&node) { - attractors.push(a); - } else { - let mut attractors = Vec::new(); - attractors.push(a); - growing_paths.insert(node, attractors); - } - } - } + let mut growing_paths: HashMap<*mut Node, Vec<&Attractor>> = HashMap::new(); + let mut influence_map = self.get_closest_node_for_attractors(); + /* for growth_cell in growing_paths { let position = calculate_new_node_position(&growth_cell, self.segment_length); for a in growth_cell.1 { if position.distance(&a.position) < self.kill_distance as f64 { - a.dead.replace(true); + a.dead = true; + } + } + } + */ + } + + fn get_closest_node_for_attractors(&self) -> HashMap<&Attractor, (&Node, f64)> { + let mut map = HashMap::new(); + self.build_attractor_map(&self.nodes, map); + + + + map + } + + // TODO use a struct instead of (&Node, f64) tuple + fn build_attractor_map<'a>(self, nodes: &'a Vec, mut map: HashMap<&'a Attractor, (&'a Node, f64)>) { + for n in nodes.iter() { + if !n.growing { + continue; + } + // TODO figure out that borrowing mess + let attractors: Vec<(&Attractor, f64)> = self.find_attractors_in_range(n); + if attractors.is_empty() { + n.growing = false; + continue; + } + + for a in attractors { + if let Some(closest) = map.get(a.0) { + if a.1 < closest.1 { + map.insert(a.0, (n, a.1)); + } + } else { + map.insert(a.0, (n, a.1)); } } - self.new_nodes - .push((growth_cell.0, Node::new(position))); } } + + fn find_attractors_in_range(&self, n: &Node) -> Vec<(&Attractor, f64)> { + let mut attractors_in_range = Vec::new(); + for a in self.attractors.iter() { + let distance = n.position.distance(&a.position); + if distance < self.attraction_distance as f64 { + attractors_in_range.push((a, distance)); + } + } + attractors_in_range + } } + #[cfg(test)] mod test { use std::cell::RefCell; @@ -259,7 +274,6 @@ mod test { sc.grow(); - assert_eq!(sc.new_nodes.len(), 0); assert!(sc .attractors .iter()