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
);
for _i in 1..15 {
for _i in 1..5 {
sc.grow();
let render_id = window().unwrap().performance().unwrap().now();
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::*;
@ -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<bool>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Node {
pub position: Point,
pub children: RefCell<Vec<Node>>,
pub children: RefCell<Vec<Rc<Node>>>,
}
impl std::hash::Hash for Node {
@ -32,7 +33,10 @@ impl std::hash::Hash for 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();
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(),
}
}
}

View File

@ -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)));
}
}

View File

@ -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<RefCell<Vec<Node>>>,
new_nodes: RefCell<Vec<(Rc<Node>, Rc<Node>)>>,
pub root_nodes: RefCell<Vec<Rc<Node>>>,
pub attractors: Rc<RefCell<Vec<Attractor>>>,
}
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<Vec<Rc<Node>>>) {
// 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<Rc<Node>, 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<Rc<Node>> = 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))));
}
}
}