space_colonization: it works! Still need to improve rendering, animation and performance but we are getting there

This commit is contained in:
Jean-Gabriel Gill-Couture 2023-07-24 23:44:08 -04:00
parent 64c3987cae
commit 51dca7ac7e
5 changed files with 155 additions and 37 deletions

View File

@ -18,7 +18,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let height = canvas_parent.client_height();
canvas.set_width(u32::try_from(width).unwrap());
canvas.set_height(u32::try_from(height).unwrap());
let mut sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap());
let sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap());
// TODO Resize on window resize
log!(
"TODO resize on window resize canvas parent size = {} {}",
@ -38,7 +38,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
context.set_fill_style(&JsValue::from("yellow"));
log!("About to render nodes");
let start_time = window().unwrap().performance().unwrap().now();
for n in sc.root_nodes.borrow().iter() {
for n in sc.nodes_tree.borrow().iter() {
context.fill_rect(n.position.x.into(), n.position.y.into(), 5.0, 5.0);
}
@ -58,12 +58,12 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let end_time = window().unwrap().performance().unwrap().now();
log!(
"Rendering {} nodes and {} attractors took {}",
sc.root_nodes.borrow().len(),
sc.nodes_tree.borrow().len(),
sc.attractors.borrow().len(),
end_time - start_time
);
for _i in 1..5 {
for _i in 1..150 {
sc.grow();
let render_id = window().unwrap().performance().unwrap().now();
context.begin_path();

View File

@ -26,7 +26,7 @@ pub fn calculate_new_node_position(
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::cell::Cell;
use super::*;
const SEGMENT_LENGTH: u16 = 5;
@ -57,7 +57,7 @@ mod tests {
for p in positions.iter().skip(1) {
attractors.push(Attractor {
position: Point::new(*p),
dead: RefCell::new(false),
dead: Cell::new(false),
});
}
Self { node, attractors }

View File

@ -1,4 +1,7 @@
use std::{cell::RefCell, rc::Rc};
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use wasm_bindgen::prelude::*;
@ -17,7 +20,16 @@ extern "C" {
#[derive(Debug)]
pub struct Attractor {
pub position: Point,
pub dead: RefCell<bool>,
pub dead: Cell<bool>,
}
impl Attractor {
pub fn new(position: (i32, i32)) -> Attractor {
Attractor {
position: Point::new(position),
dead: Cell::new(false),
}
}
}
#[derive(Debug, PartialEq, Eq)]

View File

@ -36,8 +36,8 @@ impl Point {
}
let ratio = distance as f64 / dst;
info!("X delta : {}", towards.x - self.x);
info!("Y delta : {}", towards.y - self.y);
// 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,

View File

@ -1,12 +1,11 @@
use super::math::calculate_new_node_position;
use super::{Attractor, Node, Point};
use log::info;
use rand::thread_rng;
use rand::Rng;
use std::cell::RefCell;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use web_sys::console;
use web_sys::window;
pub struct SpaceColonization {
max_point: Point,
@ -44,14 +43,19 @@ pub struct SpaceColonization {
/// If density is 10, then there will be an average distance of 10 between attractors
density: i32,
new_nodes: RefCell<Vec<(Rc<Node>, Rc<Node>)>>,
pub root_nodes: RefCell<Vec<Rc<Node>>>,
/// Tree like representation of all nodes
/// [node: [child1: [grand-child], child2: [grand-child2]]]
pub nodes_tree: RefCell<Vec<Rc<Node>>>,
/// Flat list of all nodes in the tree
/// [node, child1, grand-child, child2, grand-child2]
nodes: RefCell<Vec<Rc<Node>>>,
pub attractors: Rc<RefCell<Vec<Attractor>>>,
}
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
let root_nodes = RefCell::new(Vec::new());
root_nodes.borrow_mut().push(Rc::new(Node {
let mut nodes_vec = Vec::new();
nodes_vec.push(Rc::new(Node {
position: Point { x: 100, y: 100 },
children: RefCell::new(Vec::new()),
}));
@ -62,11 +66,12 @@ impl SpaceColonization {
x: width,
y: height,
},
kill_distance: 10,
attraction_distance: 430,
segment_length: 50,
density: 300,
root_nodes,
kill_distance: 5,
attraction_distance: 100,
segment_length: 5,
density: 30,
nodes_tree: RefCell::new(nodes_vec.clone()),
nodes: RefCell::new(nodes_vec),
attractors,
new_nodes: RefCell::new(Vec::new()),
};
@ -76,34 +81,53 @@ impl SpaceColonization {
return sc;
}
#[cfg(test)]
pub fn new_for_tests(
width: i32,
height: i32,
nodes: Vec<Rc<Node>>,
attractors: Vec<Attractor>,
) -> SpaceColonization {
SpaceColonization {
max_point: Point {
x: width,
y: height,
},
kill_distance: 5,
attraction_distance: 12,
segment_length: 3,
density: 3,
nodes_tree: RefCell::new(nodes.clone()),
nodes: RefCell::new(nodes),
attractors: Rc::new(RefCell::new(attractors)),
new_nodes: RefCell::new(Vec::new()),
}
}
pub fn render_nodes<F>(&self, render_id: f64, render_fn: F)
where
F: Copy + Fn(&Node, &Node),
{
for n in self.root_nodes.borrow().iter() {
info!("Rendering {} nodes", self.nodes.borrow().len());
for n in self.nodes_tree.borrow().iter() {
n.render(render_id, render_fn);
}
}
fn place_attractors(&mut self) {
let start_time = window().unwrap().performance().unwrap().now();
console::log_1(&format!("Start placing attractors {}", start_time).into());
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.borrow_mut().push(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()),
dead: RefCell::new(false),
dead: Cell::new(false),
});
y_pos += self.density;
}
x_pos += self.density;
y_pos = 0;
}
let end_time = window().unwrap().performance().unwrap().now();
let elapsed = end_time - start_time;
console::log_1(&format!("Done placing attractors , took : {}", elapsed).into());
}
fn get_random_point(&self, x_pos: i32, y_pos: i32) -> Point {
@ -131,40 +155,44 @@ impl SpaceColonization {
}
pub fn grow(&self) {
self.grow_nodes(&self.root_nodes);
self.grow_nodes();
println!("new nodes for iteration {:?}", self.new_nodes.borrow());
let mut nodes_mut = self.nodes.borrow_mut();
for new_pair in self.new_nodes.borrow().iter() {
new_pair.0.children.borrow_mut().push(new_pair.1.clone());
nodes_mut.push(new_pair.1.clone());
}
self.new_nodes.borrow_mut().clear();
}
pub fn grow_nodes(&self, nodes: &RefCell<Vec<Rc<Node>>>) {
pub fn grow_nodes(&self) {
// 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 nodes = nodes.borrow();
let attractors = self.attractors.borrow();
let mut growing_paths: HashMap<Rc<Node>, Vec<&Attractor>> = HashMap::new();
for a in attractors.iter() {
let a_dead_mut = a.dead.borrow();
if *a_dead_mut {
if a.dead.get() {
continue;
}
let mut closest_node: Option<Rc<Node>> = None;
let mut closest_node_distance = f64::MAX;
for n in nodes.iter() {
// TODO iterate on children nodes
for n in self.nodes.borrow().iter() {
let distance = n.position.distance(&a.position);
if distance <= self.attraction_distance as f64 {
// TODO make sure it is closest node amongs all nodes
if distance < closest_node_distance {
closest_node = Some(n.clone());
closest_node_distance = distance;
if distance < self.kill_distance as f64 {
a.dead.replace(true);
}
}
}
self.grow_nodes(&n.children);
}
if let Some(node) = closest_node {
if let Some(attractors) = growing_paths.get_mut(&node) {
@ -183,7 +211,85 @@ impl SpaceColonization {
a.dead.replace(true);
}
}
self.new_nodes.borrow_mut().push((growth_cell.0.clone(), Rc::new(Node::new(position))));
self.new_nodes
.borrow_mut()
.push((growth_cell.0.clone(), Rc::new(Node::new(position))));
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn grow_should_reach_single_attractor_and_die() {
let mut nodes = Vec::new();
nodes.push(Rc::new(Node::new(Point::new((0, 0)))));
let mut attractors = Vec::new();
attractors.push(Attractor::new((10, 0)));
let sc = SpaceColonization::new_for_tests(100, 100, nodes, attractors);
assert_eq!(sc.attractors.borrow().len(), 1);
assert!(sc
.attractors
.borrow()
.iter()
.find(|a| a.dead.get() == true)
.is_none());
assert_eq!(sc.nodes_tree.borrow()[0].children.borrow().len(), 0);
sc.grow();
assert_eq!(sc.new_nodes.borrow().len(), 0);
assert_eq!(sc.nodes_tree.borrow()[0].children.borrow().len(), 1);
assert!(sc
.attractors
.borrow()
.iter()
.find(|a| a.dead.get() == true)
.is_none());
assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow()[0].position,
Point::new((3, 0))
);
assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow().len(),
1,
);
assert_eq!(
sc.nodes_tree.borrow().len(),
1,
);
println!("root node direct children iteration 1 {:?}", sc.nodes_tree.borrow()[0].children.borrow());
sc.grow();
assert_eq!(
sc.nodes_tree.borrow().len(),
1,
);
assert_eq!(sc
.attractors
.borrow()
.iter()
.filter(|a| a.dead.get() == true)
.collect::<Vec<&Attractor>>().len(), 1);
println!("root node direct children iteration 2 {:?}", sc.nodes_tree.borrow()[0].children.borrow());
assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow().len(),
1,
);
assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow()[0].position,
Point::new((3, 0))
);
assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow()[0].children.borrow()[0].position,
Point::new((6, 0))
);
assert_eq!(sc.nodes.borrow().len(), 3);
}
}