From 97cf5b21d9982c3280e7e473ea6de190760aef5b Mon Sep 17 00:00:00 2001 From: jeangab Date: Thu, 10 Aug 2023 22:05:46 -0400 Subject: [PATCH] feat(space colonization): now pretty much works! still slowish but not too bad, about 10fps at worst on a good laptop with a ryzen 6800u --- Cargo.lock | 41 ++++ Cargo.toml | 4 + src/components/background.rs | 17 +- src/space_colonization/math.rs | 198 +++++++++++++++++-- src/space_colonization/space_colonization.rs | 64 ++++-- 5 files changed, 284 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53ebaae..6d6281b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -735,6 +735,19 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "errno" version = "0.3.2" @@ -1069,6 +1082,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.27" @@ -1427,6 +1446,7 @@ dependencies = [ "cfg-if", "console_error_panic_hook", "console_log", + "env_logger", "getrandom", "js-sys", "leptos", @@ -1436,6 +1456,7 @@ dependencies = [ "log", "rand", "simple_logger", + "test-log", "wasm-bindgen", "web-sys", ] @@ -2349,6 +2370,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-log" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9601d162c1d77e62c1ea0bc8116cd1caf143ce3af947536c3c9052a1677fe0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "thiserror" version = "1.0.44" diff --git a/Cargo.toml b/Cargo.toml index c50aaa2..1d0e56d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,3 +96,7 @@ lib-features = ["hydrate"] # # Optional. Defaults to false. lib-default-features = false + +[dev-dependencies] +test-log = "*" +env_logger = "*" diff --git a/src/components/background.rs b/src/components/background.rs index a1f8acd..16dbac1 100644 --- a/src/components/background.rs +++ b/src/components/background.rs @@ -59,10 +59,6 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { sc.render_nodes(&nodes.borrow(), render_id, render_node_fn); context.stroke(); - context.set_fill_style(&JsValue::from("magenta")); - 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 {}", @@ -80,6 +76,15 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { let render_id = window().unwrap().performance().unwrap().now(); context.begin_path(); sc.render_nodes(&nodes.borrow(), render_id, render_node_fn); + + context.set_fill_style(&JsValue::from("magenta")); + for a in sc.attractors.iter().filter(|a| a.dead) { + context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0); + } + context.set_fill_style(&JsValue::from("red")); + for a in sc.attractors.iter().filter(|a| !a.dead) { + context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0); + } context.stroke(); let end_time = window().unwrap().performance().unwrap().now(); log!( @@ -90,7 +95,9 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { }); let window = window().unwrap(); - window.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()).unwrap(); + window + .add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()) + .unwrap(); closure.forget(); }); diff --git a/src/space_colonization/math.rs b/src/space_colonization/math.rs index c97ea01..98f34c2 100644 --- a/src/space_colonization/math.rs +++ b/src/space_colonization/math.rs @@ -1,26 +1,107 @@ +use log::info; + use super::{Attractor, Node, Point}; pub fn calculate_new_node_position( node: &Node, attractors: &Vec<*mut Attractor>, segment_length: u16, -) -> Point { +) -> Option { + calculate_new_node_position_with_skip(node, attractors, segment_length, 0) +} + +pub fn calculate_new_node_position_with_skip( + node: &Node, + attractors: &Vec<*mut Attractor>, + segment_length: u16, + attractors_to_skip: usize, +) -> Option { + assert!( + attractors.len() > 0, + "There must be at least one attractor!" + ); let mut attraction_sum_x = 0; let mut attraction_sum_y = 0; - for a in attractors.iter() { - unsafe { - attraction_sum_x += (**a).position.x - node.position.x; - attraction_sum_y += (**a).position.y - node.position.y; + for a in attractors.iter().skip(attractors_to_skip) { + if attractors_to_skip > 0 { + info!("working on attractor {:?}", a); } + let attractor: &Attractor; + unsafe { + attractor = &**a; + } + + if attractors_to_skip > 0 { + info!("successfully worked with attractor {:?}", a); + } + attraction_sum_x += attractor.position.x - node.position.x; + attraction_sum_y += attractor.position.y - node.position.y; + } + + // Here this fixes what happens when the sum of the vectors towards attractors is 0 + // See test new_node_ignores_attractors_when_average_results_in_no_movement + if attraction_sum_y == 0 && attraction_sum_x == 0 { + if attractors.len() == 1 { + let a = attractors.first().unwrap(); + unsafe { + (**a).dead = true; + } + return None; + } + assert!( + attractors.len() > attractors_to_skip + 1, + "There must be more attractors in the array than the number to skip" + ); + + return calculate_new_node_position_with_skip( + node, + attractors, + segment_length, + attractors_to_skip + 1, + ); } let point = Point { - x: node.position.x + attraction_sum_x / attractors.len() as i32, - y: node.position.y + attraction_sum_y / attractors.len() as i32, + x: node.position.x + attraction_sum_x as i32, + y: node.position.y + attraction_sum_y as i32, }; - node.position.movement(point, segment_length) + let new_point = node.position.movement(point, segment_length); + println!("just did movement"); + dbg!(node.position, point, segment_length, new_point); + + let new_distance = new_point.distance(&node.position); + if new_distance > (segment_length * 2) as f64 { + info!( + "new node {:?} is far from parent {:?}", + new_point, node.position + ); + let mut readable_attractors: Vec<&Attractor> = Vec::new(); + unsafe { + attractors.iter().for_each(|a| { + readable_attractors.push(&**a); + }); + } + info!( + "Calculations inputs are + attractors {:?} + node.position {:?} + segment_length {} + point {:?}", + readable_attractors, node.position, segment_length, point + ); + + info!( + "Calculations outputs are + distance {} + attraction_sum x {} , y {} + new_point {:?}", + new_distance, attraction_sum_x, attraction_sum_y, new_point + ); + } + + Some(new_point) } #[cfg(test)] @@ -30,19 +111,97 @@ mod tests { #[test] fn new_node_moves_toward_single_attractor() { - let mut growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec()); - let attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut(); + let mut growth_cell = GrowthCell::from_positions((0, 0), [(0, 10)].to_vec()); + let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut(); let point = calculate_new_node_position( &growth_cell.node, - &attractors_as_ptr_mut, + &mut attractors_as_ptr_mut, SEGMENT_LENGTH, - ); + ) + .unwrap(); assert_eq!(point, Point::new((0, 5))); } #[test] - fn new_node_ignores_dead_attractor() {} + fn new_node_ignores_attractors_when_average_results_in_no_movement() { + let mut growth_cell = GrowthCell::from_positions((10, 0), [(0, 0), (20, 0)].to_vec()); + let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut(); + + let point = calculate_new_node_position( + &growth_cell.node, + &mut attractors_as_ptr_mut, + SEGMENT_LENGTH, + ) + .unwrap(); + assert_eq!(point, Point::new((15, 0))); + } + + #[test] + fn new_node_ignores_dead_attractor() { + // TODO + assert!(false); + } + + #[test_log::test] + fn large_number_of_attractors() { + let attractors = [ + (1048, 241), + (935, 268), + (953, 363), + (978, 333), + (988, 355), + (894, 234), + (892, 331), + (1050, 275), + (972, 261), + (882, 280), + (1013, 212), + (1023, 233), + (899, 197), + (911, 285), + (879, 291), + (977, 293), + (938, 233), + (975, 196), + (964, 295), + (967, 197), + (869, 242), + (945, 323), + (1035, 287), + (962, 243), + (1023, 297), + (952, 185), + (984, 261), + (1011, 264), + (931, 209), + (900, 281), + (1006, 319), + (929, 295), + (936, 325), + (1001, 182), + (995, 225), + ]; + let node = (960, 266); + + let mut growth_cell = GrowthCell::from_positions(node, attractors.to_vec()); + let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut(); + + let point = calculate_new_node_position( + &growth_cell.node, + &mut attractors_as_ptr_mut, + SEGMENT_LENGTH, + ) + .unwrap(); + assert_eq!(point, Point::new((965, 266))); + /* + segment_length 5 + Calculations outputs are + distance 996.1706681086329 + attraction_sum x 17 , y 1 + resulting point Point { x: 960, y: 266 } + */ + } struct GrowthCell { node: Node, @@ -50,15 +209,15 @@ mod tests { } impl GrowthCell { - pub fn from_positions(positions: Vec<(i32, i32)>) -> Self { - assert!(positions.len() >= 2); + pub fn from_positions(node: (i32, i32), attractors_positions: Vec<(i32, i32)>) -> Self { + assert!(attractors_positions.len() >= 1); let node = Node { - position: Point::new(positions[0]), + position: Point::new(node), children: Vec::new().into(), growing: true, }; let mut attractors = Vec::new(); - for p in positions.iter().skip(1) { + for p in attractors_positions.iter() { attractors.push(Attractor { position: Point::new(*p), dead: false, @@ -68,7 +227,10 @@ mod tests { } fn attractors_as_ptr_mut(&mut self) -> Vec<*mut Attractor> { - self.attractors.iter_mut().map(|a| { a as *mut Attractor }).collect() + self.attractors + .iter_mut() + .map(|a| a as *mut Attractor) + .collect() } } } diff --git a/src/space_colonization/space_colonization.rs b/src/space_colonization/space_colonization.rs index 5a776ed..6db52fe 100644 --- a/src/space_colonization/space_colonization.rs +++ b/src/space_colonization/space_colonization.rs @@ -139,6 +139,7 @@ impl SpaceColonization { // - I can efficiently render my nodes on a canvas // - I use as little memory as possible // - I can update my nodes + info!("Growing nodes {:?}", nodes); self.grow_nodes(&mut nodes) } @@ -189,20 +190,29 @@ impl SpaceColonization { // // Using raw fixed length arrays would solve that but its a fine line between too // large memory usage and enough children nodes + // + // LEARNING : using unsafe here sounded like not the worst idea and was a nice + // opportunity to learn, which did happen. I am closing in now and I can say that I + // have learned quite a lot about how rust programs work and should be designed. 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); + /* + let segfault_boy: *const u8 = usize::MAX as *const u8; + let memory_content = *segfault_boy; + println!("memory content at address 0 {}", memory_content); + */ + let new_point = + calculate_new_node_position(&(**node), attractor, self.segment_length); + if let Some(new_point) = new_point { + let new_node = Node::new(new_point); + 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); + } } }); } @@ -212,6 +222,10 @@ impl SpaceColonization { mut attractor_to_closest_node: &mut HashMap<*mut Attractor, Attraction>, n: &mut Node, ) { + for child in n.children.iter_mut() { + self.build_attractor_to_closest_node(&mut attractor_to_closest_node, child); + } + if !n.growing { return; } @@ -233,14 +247,11 @@ impl SpaceColonization { } } - 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() { + for a in self.attractors.iter_mut().filter(|a| !a.dead) { let distance = n.position.distance(&a.position); if distance < self.attraction_distance as f64 { attractors_in_range.push((a as *mut Attractor, distance)); @@ -304,7 +315,6 @@ mod test { println!("after grow 2"); dbg!(&nodes); - // TODO assert nodes 3,0 and 6,0 assert_vertices( &sc, &nodes, @@ -323,4 +333,24 @@ mod test { 1 ); } + + #[test] + fn grow_should_ignore_dead_attractors() { + let mut nodes = Vec::new(); + nodes.push(Node::new(Point::new((0, 0)))); + let mut attractors = Vec::new(); + attractors.push(Attractor { + position: Point::new((10, 0)), + dead: true, + }); + + 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_some()); + + sc.grow(&mut nodes); + + assert_vertices(&sc, &nodes, Vec::new()); + } }