From 64c3987cae2ec9949546841a0151006183ddbf42 Mon Sep 17 00:00:00 2001 From: jeangab Date: Mon, 24 Jul 2023 16:10:18 -0400 Subject: [PATCH] It works! Or does it? Have to inspect carefully --- src/components/background.rs | 2 +- src/space_colonization/log.rs | 0 src/space_colonization/math.rs | 70 ++++++++++++ src/space_colonization/mod.rs | 21 +++- src/space_colonization/point.rs | 111 +++++++++++++++---- src/space_colonization/space_colonization.rs | 91 ++++++++++----- 6 files changed, 242 insertions(+), 53 deletions(-) create mode 100644 src/space_colonization/log.rs create mode 100644 src/space_colonization/math.rs diff --git a/src/components/background.rs b/src/components/background.rs index 5399d00..678751c 100644 --- a/src/components/background.rs +++ b/src/components/background.rs @@ -63,7 +63,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { end_time - start_time ); - for _i in 1..15 { + for _i in 1..5 { sc.grow(); let render_id = window().unwrap().performance().unwrap().now(); context.begin_path(); diff --git a/src/space_colonization/log.rs b/src/space_colonization/log.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/space_colonization/math.rs b/src/space_colonization/math.rs new file mode 100644 index 0000000..6ccd6ea --- /dev/null +++ b/src/space_colonization/math.rs @@ -0,0 +1,70 @@ +use std::rc::Rc; + +use super::{Attractor, Node, Point}; + +pub fn calculate_new_node_position( + growth_cell: &(Rc, Vec<&Attractor>), + segment_length: u16, +) -> Point { + let node = &growth_cell.0; + let attractors = &growth_cell.1; + let mut attraction_sum_x = 0; + let mut attraction_sum_y = 0; + + for a in attractors.iter() { + attraction_sum_x += a.position.x - node.position.x; + attraction_sum_y += a.position.y - node.position.y; + } + + 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, + }; + + node.position.movement(point, segment_length) +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use super::*; + const SEGMENT_LENGTH: u16 = 5; + + #[test] + fn new_node_moves_toward_single_attractor() { + let growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec()); + let point = calculate_new_node_position(&growth_cell.as_refs(), SEGMENT_LENGTH); + assert_eq!(point, Point::new((0, 5))); + } + + #[test] + fn new_node_ignores_dead_attractor() {} + + struct GrowthCell { + node: Rc, + attractors: Vec, + } + + impl GrowthCell { + pub fn from_positions(positions: Vec<(i32, i32)>) -> Self { + assert!(positions.len() >= 2); + let node = Rc::new(Node { + position: Point::new(positions[0]), + children: Vec::new().into(), + }); + let mut attractors = Vec::new(); + for p in positions.iter().skip(1) { + attractors.push(Attractor { + position: Point::new(*p), + dead: RefCell::new(false), + }); + } + Self { node, attractors } + } + + fn as_refs(&self) -> (Rc, Vec<&Attractor>) { + (self.node.clone(), self.attractors.iter().collect()) + } + } +} diff --git a/src/space_colonization/mod.rs b/src/space_colonization/mod.rs index 1d8d770..16dcde4 100644 --- a/src/space_colonization/mod.rs +++ b/src/space_colonization/mod.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::{cell::RefCell, rc::Rc}; use wasm_bindgen::prelude::*; @@ -6,6 +6,7 @@ mod point; pub use point::*; mod space_colonization; pub use space_colonization::*; +mod math; #[wasm_bindgen] extern "C" { @@ -16,13 +17,13 @@ extern "C" { #[derive(Debug)] pub struct Attractor { pub position: Point, - pub dead: bool, + pub dead: RefCell, } #[derive(Debug, PartialEq, Eq)] pub struct Node { pub position: Point, - pub children: RefCell>, + pub children: RefCell>>, } impl std::hash::Hash for Node { @@ -32,7 +33,10 @@ impl std::hash::Hash for Node { } impl Node { - pub fn render(&self, render_id: f64, render_fn: F) where F: Copy + Fn(&Node, &Node) { + pub fn render(&self, render_id: f64, render_fn: F) + where + F: Copy + Fn(&Node, &Node), + { let children = self.children.borrow(); for child in children.iter() { render_fn(self, &child); @@ -42,6 +46,11 @@ impl Node { child.render(render_id, render_fn); } } + + fn new(position: Point) -> Self { + Self { + position, + children: Vec::new().into(), + } + } } - - diff --git a/src/space_colonization/point.rs b/src/space_colonization/point.rs index ed8a2be..c05e273 100644 --- a/src/space_colonization/point.rs +++ b/src/space_colonization/point.rs @@ -1,4 +1,6 @@ -#[derive(Debug, PartialEq, Eq, Hash)] +use log::info; + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Point { pub x: i32, pub y: i32, @@ -7,11 +9,11 @@ pub struct Point { impl Point { pub fn distance(&self, p: &Point) -> f64 { if self.x == p.x { - return (p.y - self.y).into(); + return (p.y - self.y).abs().into(); } if self.y == p.y { - return (p.x - self.x).into(); + return (p.x - self.x).abs().into(); } let x_distance = p.x - self.x; @@ -19,6 +21,29 @@ impl Point { return f64::from(x_distance.pow(2) + y_distance.pow(2)).sqrt(); } + + pub(crate) fn new(positions: (i32, i32)) -> Self { + Self { + x: positions.0, + y: positions.1, + } + } + + pub(crate) fn movement(&self, towards: Point, distance: u16) -> Point { + let dst = self.distance(&towards); + if dst == distance as f64 { + return towards; + } + + let ratio = distance as f64 / dst; + 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, + y: ((towards.y - self.y) as f64 * ratio + self.y as f64).round() as i32, + } + } } #[cfg(test)] @@ -51,30 +76,29 @@ mod tests { #[test] fn distance_same_y_is_x() { - let p1 = Point { - x: 1, - y: 1, - }; - - let p2 = Point { - x: 5, - y: 1, - }; + let p1 = Point::new((1,1)); + let p2 = Point::new((5,1)); assert_eq!(p1.distance(&p2), 4.0); } #[test] fn distance_3_4_5() { - let p1 = Point { - x: 0, - y: 0, - }; + let p1 = Point::new((0,0)); + let p2 = Point::new((3,4)); - let p2 = Point { - x: 3, - y: 4, - }; + assert_eq!(p1.distance(&p2), 5.0); + } + + #[test] + fn distance_is_always_positive() { + let p1 = Point::new((10,10)); + let p2 = Point::new((5,10)); + + assert_eq!(p1.distance(&p2), 5.0); + + let p1 = Point::new((10,10)); + let p2 = Point::new((10,5)); assert_eq!(p1.distance(&p2), 5.0); } @@ -138,5 +162,52 @@ mod tests { assert_eq!(p1.distance(&p2) as f32, 71.5891); } + + #[test] + fn scale_does_nothing_when_right_length() { + let root = Point::new((0,0)); + let node = Point::new((0,1)); + assert_eq!(root.movement(node.clone(), 1), node); + } + + #[test] + fn scale_adjusts_to_asked_length() { + let root = Point::new((0,0)); + let node = Point::new((0,1)); + assert_eq!(root.movement(node, 10), Point::new((0,10))); + } + + #[test] + fn scale_works_away_from_origin() { + let root = Point::new((10,10)); + let node = Point::new((10,11)); + assert_eq!(root.movement(node, 10), Point::new((10,20))); + } + + #[test] + fn scale_works_in_two_dimension() { + let root = Point::new((10,10)); + let node = Point::new((40,50)); + assert_eq!(root.movement(node, 50), Point::new((40,50))); + + let root = Point::new((10,10)); + let node = Point::new((40,50)); + assert_eq!(root.movement(node, 5), Point::new((13,14))); + } + + #[test] + fn scale_works_in_all_directions() { + let root = Point::new((40,50)); + let node = Point::new((10,10)); + assert_eq!(root.movement(node, 5), Point::new((37,46))); + + let root = Point::new((50,10)); + let node = Point::new((10,10)); + assert_eq!(root.movement(node, 5), Point::new((45,10))); + + let root = Point::new((10,50)); + let node = Point::new((10,10)); + assert_eq!(root.movement(node, 5), Point::new((10,45))); + } } diff --git a/src/space_colonization/space_colonization.rs b/src/space_colonization/space_colonization.rs index 24140fa..f989ef9 100644 --- a/src/space_colonization/space_colonization.rs +++ b/src/space_colonization/space_colonization.rs @@ -1,10 +1,10 @@ -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; - +use super::math::calculate_new_node_position; use super::{Attractor, Node, Point}; use rand::thread_rng; use rand::Rng; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; use web_sys::console; use web_sys::window; @@ -15,24 +15,46 @@ pub struct SpaceColonization { /// Maximum distance between an attractor and a node for the node to /// be affected by the attractor. /// - /// Must be greater than sqrt((density)**2 + (density)**2) + /// 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: 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 root_nodes: Rc>>, + new_nodes: RefCell, Rc)>>, + pub root_nodes: RefCell>>, pub attractors: Rc>>, } impl SpaceColonization { pub fn new(width: i32, height: i32) -> SpaceColonization { - let root_nodes = Rc::new(RefCell::new(Vec::new())); - root_nodes.borrow_mut().push(Node { + let root_nodes = RefCell::new(Vec::new()); + root_nodes.borrow_mut().push(Rc::new(Node { position: Point { x: 100, y: 100 }, children: RefCell::new(Vec::new()), - }); + })); let attractors = Rc::new(RefCell::new(Vec::new())); let mut sc = SpaceColonization { @@ -41,11 +63,12 @@ impl SpaceColonization { y: height, }, kill_distance: 10, - attraction_distance: 43, - segment_length: 5, - density: 30, + attraction_distance: 430, + segment_length: 50, + density: 300, root_nodes, attractors, + new_nodes: RefCell::new(Vec::new()), }; sc.place_attractors(); @@ -71,7 +94,7 @@ impl SpaceColonization { 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: false, + dead: RefCell::new(false), }); y_pos += self.density; } @@ -107,32 +130,44 @@ impl SpaceColonization { } } - pub fn grow(&mut self) { + pub fn grow(&self) { + self.grow_nodes(&self.root_nodes); + for new_pair in self.new_nodes.borrow().iter() { + new_pair.0.children.borrow_mut().push(new_pair.1.clone()); + } + } + + pub fn grow_nodes(&self, nodes: &RefCell>>) { // 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 root_nodes = self.root_nodes.borrow(); + let nodes = nodes.borrow(); let attractors = self.attractors.borrow(); - let mut growing_paths : HashMap<&Node, Vec<&Attractor>> = HashMap::new(); + let mut growing_paths: HashMap, Vec<&Attractor>> = HashMap::new(); for a in attractors.iter() { - let mut closest_node: Option<&Node> = None; + let a_dead_mut = a.dead.borrow(); + if *a_dead_mut { + continue; + } + let mut closest_node: Option> = None; let mut closest_node_distance = f64::MAX; - for n in root_nodes.iter() { // TODO iterate on children nodes + for n in nodes.iter() { + // TODO iterate on children nodes let distance = n.position.distance(&a.position); if distance <= self.attraction_distance as f64 { - console::log_1(&format!("found node within attraction distance {:?}", a).into()); if distance < closest_node_distance { - closest_node = Some(n); + closest_node = Some(n.clone()); closest_node_distance = distance; } } + self.grow_nodes(&n.children); } if let Some(node) = closest_node { - if let Some(attractors) = growing_paths.get_mut(node) { + if let Some(attractors) = growing_paths.get_mut(&node) { attractors.push(a); } else { let mut attractors = Vec::new(); @@ -141,10 +176,14 @@ impl SpaceColonization { } } } - console::log_1(&format!("found {:?} pairs ", growing_paths).into()); - for node in growing_paths { - todo!("calculate new node position averaging all 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); + } + } + self.new_nodes.borrow_mut().push((growth_cell.0.clone(), Rc::new(Node::new(position)))); } } - }