wip: Refactor space colonization, separate nodes vec from space colonization struct allows for simpler ownership management

This commit is contained in:
jeangab 2023-08-08 23:33:34 -04:00
parent cb9df9e79a
commit 66e1f813cf
4 changed files with 55 additions and 101 deletions

View File

@ -5,6 +5,7 @@ use wasm_bindgen::JsValue;
use web_sys::window;
use crate::space_colonization::Node;
use crate::space_colonization::Point;
use crate::space_colonization::SpaceColonization;
#[component]
@ -19,6 +20,8 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
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 mut nodes = Vec::new();
nodes.push(Node::new(Point::new((100, 100))));
// TODO Resize on window resize
log!(
"TODO resize on window resize canvas parent size = {} {}",
@ -44,7 +47,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
context.line_to(child.position.x.into(), child.position.y.into());
};
let render_id = window().unwrap().performance().unwrap().now();
sc.render_nodes(render_id, render_node_fn);
sc.render_nodes(&nodes, render_id, render_node_fn);
context.stroke();
context.set_fill_style(&JsValue::from("magenta"));
@ -59,10 +62,10 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
);
for _i in 1..150 {
sc.grow();
nodes = sc.grow(nodes);
let render_id = window().unwrap().performance().unwrap().now();
context.begin_path();
sc.render_nodes(render_id, render_node_fn);
sc.render_nodes(&nodes, render_id, render_node_fn);
context.stroke();
}
});

View File

@ -30,7 +30,8 @@ mod tests {
#[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);
let point = calculate_new_node_position(&(growth_cell.node, growth_cell.attractors.iter().collect()), SEGMENT_LENGTH);
assert_eq!(point, Point::new((0, 5)));
}
@ -48,6 +49,7 @@ mod tests {
let node = Node {
position: Point::new(positions[0]),
children: Vec::new().into(),
growing: true,
};
let mut attractors = Vec::new();
for p in positions.iter().skip(1) {
@ -58,9 +60,5 @@ mod tests {
}
Self { node, attractors }
}
fn as_refs(&self) -> (Node, Vec<&Attractor>) {
(self.node, self.attractors.iter().collect())
}
}
}

View File

@ -66,17 +66,16 @@ impl Node {
where
F: Copy + Fn(&Node, &Node),
{
let children = self.children;
for child in children.iter() {
for child in self.children.iter() {
render_fn(self, &child);
}
for child in children.iter() {
for child in self.children.iter() {
child.render(render_id, render_fn);
}
}
fn new(position: Point) -> Self {
pub fn new(position: Point) -> Self {
Self {
position,
growing: true,

View File

@ -39,29 +39,11 @@ pub struct SpaceColonization {
///
/// If density is 10, then there will be an average distance of 10 between attractors
density: i32,
/// Tree of all nodes in the space. There can be multiple root nodes.
/// ```yaml
/// [
/// node: {
/// position: (x,y),
/// children : [
/// child1 : {
/// position, children: [ grand-child1, grand-child2 ]
/// },
/// child2 : {...}
/// ],
/// },
/// node2: { ...another tree },
/// ]
/// ```
nodes: Vec<Node>,
pub attractors: Vec<Attractor>,
}
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
let mut nodes_vec = Vec::new();
nodes_vec.push(Node::new(Point::new((100, 100))));
let attractors = Vec::new();
let mut sc = SpaceColonization {
@ -73,7 +55,6 @@ impl SpaceColonization {
attraction_distance: 100,
segment_length: 5,
density: 30,
nodes: nodes_vec,
attractors,
};
@ -86,7 +67,6 @@ impl SpaceColonization {
pub fn new_for_tests(
width: i32,
height: i32,
nodes: Vec<Node>,
attractors: Vec<Attractor>,
) -> SpaceColonization {
SpaceColonization {
@ -98,17 +78,16 @@ impl SpaceColonization {
attraction_distance: 12,
segment_length: 3,
density: 3,
nodes,
attractors,
}
}
pub fn render_nodes<F>(&self, render_id: f64, render_fn: F)
pub fn render_nodes<F>(&self, nodes: &Vec<Node>, render_id: f64, render_fn: F)
where
F: Copy + Fn(&Node, &Node),
{
info!("Rendering {} nodes", self.nodes.len());
for n in self.nodes.iter() {
info!("Rendering {} nodes", nodes.len());
for n in nodes.iter() {
n.render(render_id, render_fn);
}
}
@ -153,7 +132,7 @@ impl SpaceColonization {
}
}
pub fn grow(&mut self) {
pub fn grow(&mut self, nodes: Vec<Node>) -> Vec<Node> {
// TODO
// [x] Find a clean API that will be stable across refactoring
// [ ] Write the test against this api including performance
@ -163,62 +142,43 @@ impl SpaceColonization {
// - I can efficiently render my nodes on a canvas
// - I use as little memory as possible
// - I can update my nodes
self.grow_nodes();
self.grow_nodes(nodes)
}
pub fn grow_nodes(&mut self) {
pub fn grow_nodes(&mut self, nodes: Vec<Node>) -> Vec<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 mut growing_paths: HashMap<*mut Node, Vec<&Attractor>> = HashMap::new();
let mut influence_map = self.get_closest_node_for_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 = true;
}
}
}
*/
}
let mut influence_map: HashMap<&Attractor, (*mut Node, f64)> = HashMap::new();
fn get_closest_node_for_attractors(&self) -> HashMap<&Attractor, (&Node, f64)> {
let mut map = HashMap::new();
self.build_attractor_map(&self.nodes, map);
map
}
// TODO use a struct instead of (&Node, f64) tuple
fn build_attractor_map<'a>(self, nodes: &'a Vec<Node>, mut map: HashMap<&'a Attractor, (&'a Node, f64)>) {
for n in nodes.iter() {
let nodes = nodes.into_iter().map(|mut n| {
if !n.growing {
continue;
}
// TODO figure out that borrowing mess
let attractors: Vec<(&Attractor, f64)> = self.find_attractors_in_range(n);
if attractors.is_empty() {
n.growing = false;
continue;
return n;
}
for a in attractors {
if let Some(closest) = map.get(a.0) {
let attractors_in_range = self.find_attractors_in_range(&n);
if attractors_in_range.is_empty() {
n.growing = false;
return n;
}
for a in attractors_in_range {
if let Some(closest) = influence_map.get(a.0) {
if a.1 < closest.1 {
map.insert(a.0, (n, a.1));
influence_map.insert(a.0, (&mut n, a.1));
}
} else {
map.insert(a.0, (n, a.1));
}
influence_map.insert(a.0, (&mut n, a.1));
}
}
n
}).collect();
nodes
}
fn find_attractors_in_range(&self, n: &Node) -> Vec<(&Attractor, f64)> {
@ -233,16 +193,15 @@ impl SpaceColonization {
}
}
#[cfg(test)]
mod test {
use std::cell::RefCell;
use super::*;
fn assert_nodes(sc: &SpaceColonization, expected_nodes: Vec<(Point, Point)>) {
fn assert_nodes(sc: &SpaceColonization, nodes: &Vec<Node>, expected_nodes: Vec<(Point, Point)>) {
let rendered_nodes = RefCell::new(Vec::new());
sc.render_nodes(0.0, |n1, n2| {
sc.render_nodes(&nodes, 0.0, |n1, n2| {
rendered_nodes.borrow_mut().push((n1.position, n2.position));
});
@ -252,7 +211,7 @@ mod test {
}
return line1.1.cmp(&line2.1);
})
});
}
#[test]
@ -262,34 +221,29 @@ mod test {
let mut attractors = Vec::new();
attractors.push(Attractor::new((10, 0)));
let sc = SpaceColonization::new_for_tests(100, 100, nodes, attractors);
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors);
assert_nodes(&sc, Vec::from([(Point::new((0,0)), Point::new((10,0)))]));
assert_nodes(&sc, &nodes, Vec::from([(Point::new((0, 0)), Point::new((10, 0)))]));
assert_eq!(sc.attractors.len(), 1);
assert!(sc
.attractors
.iter()
.find(|a| a.dead == true)
.is_none());
assert!(sc.attractors.iter().find(|a| a.dead == true).is_none());
sc.grow();
nodes = sc.grow(nodes);
assert!(sc
.attractors
.iter()
.find(|a| a.dead == true)
.is_none());
assert!(sc.attractors.iter().find(|a| a.dead == true).is_none());
// TODO assert point 3,0
sc.grow();
nodes = sc.grow(nodes);
assert_eq!(sc
.attractors
assert_eq!(
sc.attractors
.iter()
.filter(|a| a.dead == true)
.collect::<Vec<&Attractor>>().len(), 1);
.collect::<Vec<&Attractor>>()
.len(),
1
);
// TODO assert nodes 3,0 and 6,0
assert_eq!(sc.nodes.len(), 3);
assert_eq!(nodes.len(), 3);
}
}