From 51dca7ac7ed245bf43ad8690054ead892d31afc0 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Mon, 24 Jul 2023 23:44:08 -0400 Subject: [PATCH] space_colonization: it works! Still need to improve rendering, animation and performance but we are getting there --- src/components/background.rs | 8 +- src/space_colonization/math.rs | 4 +- src/space_colonization/mod.rs | 16 +- src/space_colonization/point.rs | 4 +- src/space_colonization/space_colonization.rs | 160 +++++++++++++++---- 5 files changed, 155 insertions(+), 37 deletions(-) diff --git a/src/components/background.rs b/src/components/background.rs index 678751c..28b0fea 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 mut sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap()); + let 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,7 +38,7 @@ 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.root_nodes.borrow().iter() { + for n in sc.nodes_tree.borrow().iter() { context.fill_rect(n.position.x.into(), n.position.y.into(), 5.0, 5.0); } @@ -58,12 +58,12 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { let end_time = window().unwrap().performance().unwrap().now(); log!( "Rendering {} nodes and {} attractors took {}", - sc.root_nodes.borrow().len(), + sc.nodes_tree.borrow().len(), sc.attractors.borrow().len(), end_time - start_time ); - for _i in 1..5 { + for _i in 1..150 { sc.grow(); let render_id = window().unwrap().performance().unwrap().now(); context.begin_path(); diff --git a/src/space_colonization/math.rs b/src/space_colonization/math.rs index 6ccd6ea..25b946f 100644 --- a/src/space_colonization/math.rs +++ b/src/space_colonization/math.rs @@ -26,7 +26,7 @@ pub fn calculate_new_node_position( #[cfg(test)] mod tests { - use std::cell::RefCell; + use std::cell::Cell; use super::*; const SEGMENT_LENGTH: u16 = 5; @@ -57,7 +57,7 @@ mod tests { for p in positions.iter().skip(1) { attractors.push(Attractor { position: Point::new(*p), - dead: RefCell::new(false), + dead: Cell::new(false), }); } Self { node, attractors } diff --git a/src/space_colonization/mod.rs b/src/space_colonization/mod.rs index 16dcde4..f447d09 100644 --- a/src/space_colonization/mod.rs +++ b/src/space_colonization/mod.rs @@ -1,4 +1,7 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; use wasm_bindgen::prelude::*; @@ -17,7 +20,16 @@ extern "C" { #[derive(Debug)] pub struct Attractor { pub position: Point, - pub dead: RefCell, + pub dead: Cell, +} + +impl Attractor { + pub fn new(position: (i32, i32)) -> Attractor { + Attractor { + position: Point::new(position), + dead: Cell::new(false), + } + } } #[derive(Debug, PartialEq, Eq)] diff --git a/src/space_colonization/point.rs b/src/space_colonization/point.rs index c05e273..fee5062 100644 --- a/src/space_colonization/point.rs +++ b/src/space_colonization/point.rs @@ -36,8 +36,8 @@ impl Point { } let ratio = distance as f64 / dst; - info!("X delta : {}", towards.x - self.x); - info!("Y delta : {}", towards.y - self.y); + // info!("X delta : {}", towards.x - self.x); + // info!("Y delta : {}", towards.y - self.y); Point { x: ((towards.x - self.x) as f64 * ratio + self.x as f64).round() as i32, diff --git a/src/space_colonization/space_colonization.rs b/src/space_colonization/space_colonization.rs index f989ef9..0fbf471 100644 --- a/src/space_colonization/space_colonization.rs +++ b/src/space_colonization/space_colonization.rs @@ -1,12 +1,11 @@ use super::math::calculate_new_node_position; use super::{Attractor, Node, Point}; +use log::info; use rand::thread_rng; use rand::Rng; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::rc::Rc; -use web_sys::console; -use web_sys::window; pub struct SpaceColonization { max_point: Point, @@ -44,14 +43,19 @@ pub struct SpaceColonization { /// If density is 10, then there will be an average distance of 10 between attractors density: i32, new_nodes: RefCell, Rc)>>, - pub root_nodes: RefCell>>, + /// Tree like representation of all nodes + /// [node: [child1: [grand-child], child2: [grand-child2]]] + pub nodes_tree: RefCell>>, + /// Flat list of all nodes in the tree + /// [node, child1, grand-child, child2, grand-child2] + nodes: RefCell>>, pub attractors: Rc>>, } impl SpaceColonization { pub fn new(width: i32, height: i32) -> SpaceColonization { - let root_nodes = RefCell::new(Vec::new()); - root_nodes.borrow_mut().push(Rc::new(Node { + let mut nodes_vec = Vec::new(); + nodes_vec.push(Rc::new(Node { position: Point { x: 100, y: 100 }, children: RefCell::new(Vec::new()), })); @@ -62,11 +66,12 @@ impl SpaceColonization { x: width, y: height, }, - kill_distance: 10, - attraction_distance: 430, - segment_length: 50, - density: 300, - root_nodes, + kill_distance: 5, + attraction_distance: 100, + segment_length: 5, + density: 30, + nodes_tree: RefCell::new(nodes_vec.clone()), + nodes: RefCell::new(nodes_vec), attractors, new_nodes: RefCell::new(Vec::new()), }; @@ -76,34 +81,53 @@ impl SpaceColonization { return sc; } + #[cfg(test)] + pub fn new_for_tests( + width: i32, + height: i32, + nodes: Vec>, + attractors: Vec, + ) -> SpaceColonization { + SpaceColonization { + max_point: Point { + x: width, + y: height, + }, + kill_distance: 5, + attraction_distance: 12, + segment_length: 3, + density: 3, + nodes_tree: RefCell::new(nodes.clone()), + nodes: RefCell::new(nodes), + attractors: Rc::new(RefCell::new(attractors)), + new_nodes: RefCell::new(Vec::new()), + } + } + pub fn render_nodes(&self, render_id: f64, render_fn: F) where F: Copy + Fn(&Node, &Node), { - for n in self.root_nodes.borrow().iter() { + info!("Rendering {} nodes", self.nodes.borrow().len()); + for n in self.nodes_tree.borrow().iter() { n.render(render_id, render_fn); } } fn place_attractors(&mut self) { - let start_time = window().unwrap().performance().unwrap().now(); - console::log_1(&format!("Start placing attractors {}", start_time).into()); let mut x_pos = 0; let mut y_pos = 0; while x_pos < self.max_point.x { while y_pos < self.max_point.y { self.attractors.borrow_mut().push(Attractor { position: self.get_random_point(x_pos.into(), y_pos.into()), - dead: RefCell::new(false), + dead: Cell::new(false), }); y_pos += self.density; } x_pos += self.density; y_pos = 0; } - let end_time = window().unwrap().performance().unwrap().now(); - let elapsed = end_time - start_time; - console::log_1(&format!("Done placing attractors , took : {}", elapsed).into()); } fn get_random_point(&self, x_pos: i32, y_pos: i32) -> Point { @@ -131,40 +155,44 @@ impl SpaceColonization { } pub fn grow(&self) { - self.grow_nodes(&self.root_nodes); + self.grow_nodes(); + println!("new nodes for iteration {:?}", self.new_nodes.borrow()); + let mut nodes_mut = self.nodes.borrow_mut(); for new_pair in self.new_nodes.borrow().iter() { new_pair.0.children.borrow_mut().push(new_pair.1.clone()); + nodes_mut.push(new_pair.1.clone()); } + self.new_nodes.borrow_mut().clear(); } - pub fn grow_nodes(&self, nodes: &RefCell>>) { + pub fn grow_nodes(&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 nodes = nodes.borrow(); let attractors = self.attractors.borrow(); let mut growing_paths: HashMap, Vec<&Attractor>> = HashMap::new(); for a in attractors.iter() { - let a_dead_mut = a.dead.borrow(); - if *a_dead_mut { + if a.dead.get() { continue; } let mut closest_node: Option> = None; let mut closest_node_distance = f64::MAX; - for n in nodes.iter() { - // TODO iterate on children nodes + for n in self.nodes.borrow().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.clone()); closest_node_distance = distance; + if distance < self.kill_distance as f64 { + a.dead.replace(true); + } } } - self.grow_nodes(&n.children); } if let Some(node) = closest_node { if let Some(attractors) = growing_paths.get_mut(&node) { @@ -183,7 +211,85 @@ impl SpaceColonization { a.dead.replace(true); } } - self.new_nodes.borrow_mut().push((growth_cell.0.clone(), Rc::new(Node::new(position)))); + self.new_nodes + .borrow_mut() + .push((growth_cell.0.clone(), Rc::new(Node::new(position)))); } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn grow_should_reach_single_attractor_and_die() { + let mut nodes = Vec::new(); + nodes.push(Rc::new(Node::new(Point::new((0, 0))))); + let mut attractors = Vec::new(); + attractors.push(Attractor::new((10, 0))); + + let sc = SpaceColonization::new_for_tests(100, 100, nodes, attractors); + + assert_eq!(sc.attractors.borrow().len(), 1); + assert!(sc + .attractors + .borrow() + .iter() + .find(|a| a.dead.get() == true) + .is_none()); + assert_eq!(sc.nodes_tree.borrow()[0].children.borrow().len(), 0); + + sc.grow(); + + assert_eq!(sc.new_nodes.borrow().len(), 0); + assert_eq!(sc.nodes_tree.borrow()[0].children.borrow().len(), 1); + assert!(sc + .attractors + .borrow() + .iter() + .find(|a| a.dead.get() == true) + .is_none()); + assert_eq!( + sc.nodes_tree.borrow()[0].children.borrow()[0].position, + Point::new((3, 0)) + ); + assert_eq!( + sc.nodes_tree.borrow()[0].children.borrow().len(), + 1, + ); + assert_eq!( + sc.nodes_tree.borrow().len(), + 1, + ); + println!("root node direct children iteration 1 {:?}", sc.nodes_tree.borrow()[0].children.borrow()); + + sc.grow(); + + assert_eq!( + sc.nodes_tree.borrow().len(), + 1, + ); + assert_eq!(sc + .attractors + .borrow() + .iter() + .filter(|a| a.dead.get() == true) + .collect::>().len(), 1); + + println!("root node direct children iteration 2 {:?}", sc.nodes_tree.borrow()[0].children.borrow()); + assert_eq!( + sc.nodes_tree.borrow()[0].children.borrow().len(), + 1, + ); + assert_eq!( + sc.nodes_tree.borrow()[0].children.borrow()[0].position, + Point::new((3, 0)) + ); + assert_eq!( + sc.nodes_tree.borrow()[0].children.borrow()[0].children.borrow()[0].position, + Point::new((6, 0)) + ); + assert_eq!(sc.nodes.borrow().len(), 3); + } +}