It works! Or does it? Have to inspect carefully

This commit is contained in:
jeangab 2023-07-24 16:10:18 -04:00
parent 93781cae4a
commit 64c3987cae
6 changed files with 242 additions and 53 deletions

View File

@ -63,7 +63,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
end_time - start_time end_time - start_time
); );
for _i in 1..15 { for _i in 1..5 {
sc.grow(); sc.grow();
let render_id = window().unwrap().performance().unwrap().now(); let render_id = window().unwrap().performance().unwrap().now();
context.begin_path(); context.begin_path();

View File

View File

@ -0,0 +1,70 @@
use std::rc::Rc;
use super::{Attractor, Node, Point};
pub fn calculate_new_node_position(
growth_cell: &(Rc<Node>, 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<Node>,
attractors: Vec<Attractor>,
}
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<Node>, Vec<&Attractor>) {
(self.node.clone(), self.attractors.iter().collect())
}
}
}

View File

@ -1,4 +1,4 @@
use std::cell::RefCell; use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -6,6 +6,7 @@ mod point;
pub use point::*; pub use point::*;
mod space_colonization; mod space_colonization;
pub use space_colonization::*; pub use space_colonization::*;
mod math;
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
@ -16,13 +17,13 @@ extern "C" {
#[derive(Debug)] #[derive(Debug)]
pub struct Attractor { pub struct Attractor {
pub position: Point, pub position: Point,
pub dead: bool, pub dead: RefCell<bool>,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Node { pub struct Node {
pub position: Point, pub position: Point,
pub children: RefCell<Vec<Node>>, pub children: RefCell<Vec<Rc<Node>>>,
} }
impl std::hash::Hash for Node { impl std::hash::Hash for Node {
@ -32,7 +33,10 @@ impl std::hash::Hash for Node {
} }
impl Node { impl Node {
pub fn render<F>(&self, render_id: f64, render_fn: F) where F: Copy + Fn(&Node, &Node) { pub fn render<F>(&self, render_id: f64, render_fn: F)
where
F: Copy + Fn(&Node, &Node),
{
let children = self.children.borrow(); let children = self.children.borrow();
for child in children.iter() { for child in children.iter() {
render_fn(self, &child); render_fn(self, &child);
@ -42,6 +46,11 @@ impl Node {
child.render(render_id, render_fn); child.render(render_id, render_fn);
} }
} }
fn new(position: Point) -> Self {
Self {
position,
children: Vec::new().into(),
}
}
} }

View File

@ -1,4 +1,6 @@
#[derive(Debug, PartialEq, Eq, Hash)] use log::info;
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Point { pub struct Point {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
@ -7,11 +9,11 @@ pub struct Point {
impl Point { impl Point {
pub fn distance(&self, p: &Point) -> f64 { pub fn distance(&self, p: &Point) -> f64 {
if self.x == p.x { if self.x == p.x {
return (p.y - self.y).into(); return (p.y - self.y).abs().into();
} }
if self.y == p.y { 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; 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(); 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)] #[cfg(test)]
@ -51,30 +76,29 @@ mod tests {
#[test] #[test]
fn distance_same_y_is_x() { fn distance_same_y_is_x() {
let p1 = Point { let p1 = Point::new((1,1));
x: 1, let p2 = Point::new((5,1));
y: 1,
};
let p2 = Point {
x: 5,
y: 1,
};
assert_eq!(p1.distance(&p2), 4.0); assert_eq!(p1.distance(&p2), 4.0);
} }
#[test] #[test]
fn distance_3_4_5() { fn distance_3_4_5() {
let p1 = Point { let p1 = Point::new((0,0));
x: 0, let p2 = Point::new((3,4));
y: 0,
};
let p2 = Point { assert_eq!(p1.distance(&p2), 5.0);
x: 3, }
y: 4,
}; #[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); assert_eq!(p1.distance(&p2), 5.0);
} }
@ -138,5 +162,52 @@ mod tests {
assert_eq!(p1.distance(&p2) as f32, 71.5891); 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)));
}
} }

View File

@ -1,10 +1,10 @@
use std::cell::RefCell; use super::math::calculate_new_node_position;
use std::collections::HashMap;
use std::rc::Rc;
use super::{Attractor, Node, Point}; use super::{Attractor, Node, Point};
use rand::thread_rng; use rand::thread_rng;
use rand::Rng; use rand::Rng;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use web_sys::console; use web_sys::console;
use web_sys::window; use web_sys::window;
@ -15,24 +15,46 @@ pub struct SpaceColonization {
/// Maximum distance between an attractor and a node for the node to /// Maximum distance between an attractor and a node for the node to
/// be affected by the attractor. /// 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, attraction_distance: i32,
segment_length: i32, segment_length: u16,
/// Size of the cells on which attractors are placed. /// Size of the cells on which attractors are placed.
/// ///
/// If density is 10, then there will be an average distance of 10 between attractors /// If density is 10, then there will be an average distance of 10 between attractors
density: i32, density: i32,
pub root_nodes: Rc<RefCell<Vec<Node>>>, new_nodes: RefCell<Vec<(Rc<Node>, Rc<Node>)>>,
pub root_nodes: RefCell<Vec<Rc<Node>>>,
pub attractors: Rc<RefCell<Vec<Attractor>>>, pub attractors: Rc<RefCell<Vec<Attractor>>>,
} }
impl SpaceColonization { impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization { pub fn new(width: i32, height: i32) -> SpaceColonization {
let root_nodes = Rc::new(RefCell::new(Vec::new())); let root_nodes = RefCell::new(Vec::new());
root_nodes.borrow_mut().push(Node { root_nodes.borrow_mut().push(Rc::new(Node {
position: Point { x: 100, y: 100 }, position: Point { x: 100, y: 100 },
children: RefCell::new(Vec::new()), children: RefCell::new(Vec::new()),
}); }));
let attractors = Rc::new(RefCell::new(Vec::new())); let attractors = Rc::new(RefCell::new(Vec::new()));
let mut sc = SpaceColonization { let mut sc = SpaceColonization {
@ -41,11 +63,12 @@ impl SpaceColonization {
y: height, y: height,
}, },
kill_distance: 10, kill_distance: 10,
attraction_distance: 43, attraction_distance: 430,
segment_length: 5, segment_length: 50,
density: 30, density: 300,
root_nodes, root_nodes,
attractors, attractors,
new_nodes: RefCell::new(Vec::new()),
}; };
sc.place_attractors(); sc.place_attractors();
@ -71,7 +94,7 @@ impl SpaceColonization {
while y_pos < self.max_point.y { while y_pos < self.max_point.y {
self.attractors.borrow_mut().push(Attractor { self.attractors.borrow_mut().push(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()), position: self.get_random_point(x_pos.into(), y_pos.into()),
dead: false, dead: RefCell::new(false),
}); });
y_pos += self.density; 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<Vec<Rc<Node>>>) {
// iterate through attractors // iterate through attractors
// find closest node within attraction range // find closest node within attraction range
// build a map of nodes to affecting attractors // build a map of nodes to affecting attractors
// attractors within the attraction range that this node is the closest to // attractors within the attraction range that this node is the closest to
// //
// calculate new node position // calculate new node position
let root_nodes = self.root_nodes.borrow(); let nodes = nodes.borrow();
let attractors = self.attractors.borrow(); let attractors = self.attractors.borrow();
let mut growing_paths : HashMap<&Node, Vec<&Attractor>> = HashMap::new(); let mut growing_paths: HashMap<Rc<Node>, Vec<&Attractor>> = HashMap::new();
for a in attractors.iter() { 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<Rc<Node>> = None;
let mut closest_node_distance = f64::MAX; 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); let distance = n.position.distance(&a.position);
if distance <= self.attraction_distance as f64 { if distance <= self.attraction_distance as f64 {
console::log_1(&format!("found node within attraction distance {:?}", a).into());
if distance < closest_node_distance { if distance < closest_node_distance {
closest_node = Some(n); closest_node = Some(n.clone());
closest_node_distance = distance; closest_node_distance = distance;
} }
} }
self.grow_nodes(&n.children);
} }
if let Some(node) = closest_node { 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); attractors.push(a);
} else { } else {
let mut attractors = Vec::new(); let mut attractors = Vec::new();
@ -141,10 +176,14 @@ impl SpaceColonization {
} }
} }
} }
console::log_1(&format!("found {:?} pairs ", growing_paths).into()); for growth_cell in growing_paths {
for node in growing_paths { let position = calculate_new_node_position(&growth_cell, self.segment_length);
todo!("calculate new node position averaging all attractors"); 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))));
} }
} }
} }