use super::math::calculate_new_node_position; use super::Attraction; use super::{Attractor, Node, Point}; use log::info; use rand::thread_rng; use rand::Rng; use std::collections::HashMap; pub struct SpaceColonization { max_point: Point, /// When a node grows within kill_distance of an attractor, the attractor is killed kill_distance: f64, /// Maximum distance between an attractor and a node for the node to /// be affected by the attractor. /// /// Must be greater than 2 * sqrt((density)**2 + (density)**2) + kill_distance /// The edge case that must be covered is : /// /// - There are two attractors and one node like this : /// ```text /// --------------- /// |A | | /// | | | /// | | | /// --------------- /// | | | /// | | | /// | | A| /// --------------- N /// ``` /// /// - An attractor is at the top-left corner of its cell /// - The other attractor is at the bottom-right corner of its cell /// - The Node is placed at the bottom-right limit of the kill distance of the bottom-right /// attractor /// - On the next iteration, the bottom-right attractor will be dead so the nearest attractor /// is ( 2 * cell\_diagonal + kill\_distance ) away. attraction_distance: i32, segment_length: u16, /// Size of the cells on which attractors are placed. /// /// If density is 10, then there will be an average distance of 10 between attractors density: i32, pub attractors: Vec, } impl SpaceColonization { pub fn new(width: i32, height: i32) -> SpaceColonization { let attractors = Vec::new(); let mut sc = SpaceColonization { max_point: Point { x: width, y: height, }, kill_distance: 5.0, attraction_distance: 100, segment_length: 5, density: 30, attractors, }; sc.place_attractors(); return sc; } #[cfg(test)] pub fn new_for_tests(width: i32, height: i32, attractors: Vec) -> SpaceColonization { SpaceColonization { max_point: Point { x: width, y: height, }, kill_distance: 5.0, attraction_distance: 12, segment_length: 3, density: 3, attractors, } } pub fn render_nodes(&self, nodes: &Vec, render_id: f64, render_fn: F) where F: Copy + Fn(&Node, &Node), { info!("Rendering {} nodes", nodes.len()); for n in nodes.iter() { n.render(render_id, render_fn); } } fn place_attractors(&mut self) { 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.push(Attractor::new( self.get_random_point(x_pos.into(), y_pos.into()), )); y_pos += self.density; } x_pos += self.density; y_pos = 0; } } fn get_random_point(&self, x_pos: i32, y_pos: i32) -> Point { let half_density: i32 = (self.density / 2).into(); let mut x_min = x_pos - half_density; if x_min < 0 { x_min = 0; } let mut y_min = y_pos - half_density; if y_min < 0 { y_min = 0; } Point { x: thread_rng() .gen_range(x_min..x_pos + half_density) .try_into() .unwrap(), y: thread_rng() .gen_range(y_min..y_pos + half_density) .try_into() .unwrap(), } } pub fn grow<'a>(&mut self, mut nodes: &'a mut Vec) { // TODO // [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(&mut nodes) } pub fn grow_nodes(&mut self, nodes: &mut Vec) { // 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 // ------------ START OF BLOCK ---------- // DO NOT MODIFY THE NODES VEC AFTER THIS // We are taking raw pointers to Node to be dereferenced later, if the Vec of nodes is // modified it will cause wrong behavior or segmentation faults and crash let mut attractor_to_closest_node: HashMap<*mut Attractor, Attraction> = HashMap::new(); for n in nodes.iter_mut() { self.build_attractor_to_closest_node(&mut attractor_to_closest_node, n); } let mut node_to_attractors: HashMap<*mut Node, Vec<*mut Attractor>> = HashMap::new(); attractor_to_closest_node .drain() .for_each(|(attractor, attraction)| { let mut node_attractors = match node_to_attractors.remove(&attraction.node) { Some(node_a) => node_a, None => Vec::new(), }; node_attractors.push(attractor); node_to_attractors.insert(attraction.node, node_attractors); }); let mut dead_attractors: Vec<*mut Attractor> = Vec::new(); node_to_attractors.iter().for_each(|(node, attractor)| { // Unsafe is used here for two main reasons : // // PERFORMANCE : Using unsafe here allows to store multiple mutable references to a // Node and save a few bytes of memory and cpu cycles to store a struct that holds // a fake reference to the Node, such as the path in the tree, and then resolve it // handling the Option<> every step of the way. // // Using a raw pointer we can Oh I actually just realised having a raw pointer deep // in a tree of Vec that are getting pushed into might very well cause the pointer // to become invalid when its parent gets pushed into and moved to another memory // space // // Using raw fixed length arrays would solve that but its a fine line between too // large memory usage and enough children nodes unsafe { let new_node = Node::new(calculate_new_node_position( &(**node), attractor, self.segment_length, )); attractor.iter().for_each(|a| { if (**a).position.distance(&new_node.position) <= self.kill_distance { dead_attractors.push(*a); (**a).dead = true; } }); (**node).children.push(new_node); } }); } fn build_attractor_to_closest_node<'a>( &'a mut self, mut attractor_to_closest_node: &mut HashMap<*mut Attractor, Attraction>, n: &mut Node, ) { if !n.growing { return; } let attractors_in_range = self.find_attractors_in_range(&n); if attractors_in_range.is_empty() { n.growing = false; return; } for a in attractors_in_range { if let Some(closest) = attractor_to_closest_node.get(&a.0) { if a.1 < closest.distance { attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1)); } } else { attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1)); } } for child in n.children.iter_mut() { self.build_attractor_to_closest_node(&mut attractor_to_closest_node, child); } } fn find_attractors_in_range(&mut self, n: &Node) -> Vec<(*mut Attractor, f64)> { let mut attractors_in_range = Vec::new(); for a in self.attractors.iter_mut() { let distance = n.position.distance(&a.position); if distance < self.attraction_distance as f64 { attractors_in_range.push((a as *mut Attractor, distance)); } } attractors_in_range } } #[cfg(test)] mod test { use std::cell::RefCell; use super::*; fn assert_vertices( sc: &SpaceColonization, nodes: &Vec, mut expected_nodes: Vec<(Point, Point)>, ) { let rendered_nodes = RefCell::new(Vec::new()); sc.render_nodes(&nodes, 0.0, |n1, n2| { rendered_nodes.borrow_mut().push((n1.position, n2.position)); }); let sort_points = |line1: &(Point, Point), line2: &(Point, Point)| { if line1.0 != line2.0 { return line1.0.cmp(&line2.0); } return line1.1.cmp(&line2.1); }; rendered_nodes.borrow_mut().sort_by(sort_points); expected_nodes.sort_by(sort_points); let rendered_nodes = rendered_nodes.take(); assert_eq!(rendered_nodes, expected_nodes); } #[test] fn grow_should_reach_single_attractor_and_die() { let mut nodes = Vec::new(); nodes.push(Node::new(Point::new((0, 0)))); let mut attractors = Vec::new(); attractors.push(Attractor::new(Point::new((10, 0)))); let mut sc = SpaceColonization::new_for_tests(100, 100, attractors); assert_eq!(sc.attractors.len(), 1); assert!(sc.attractors.iter().find(|a| a.dead == true).is_none()); println!("before grow"); dbg!(&nodes); sc.grow(&mut nodes); println!("after grow 1"); dbg!(&nodes); assert!(sc.attractors.iter().find(|a| a.dead == true).is_none()); sc.grow(&mut nodes); println!("after grow 2"); dbg!(&nodes); // TODO assert nodes 3,0 and 6,0 assert_vertices( &sc, &nodes, Vec::from([ (Point::new((0, 0)), Point::new((3, 0))), (Point::new((3, 0)), Point::new((6, 0))), ]), ); assert_eq!( sc.attractors .iter() .filter(|a| a.dead == true) .collect::>() .len(), 1 ); } }