wip: Refactor space colonization, separate nodes vec from space colonization struct allows for simpler ownership management
This commit is contained in:
parent
cb9df9e79a
commit
66e1f813cf
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
.iter()
|
||||
.filter(|a| a.dead == true)
|
||||
.collect::<Vec<&Attractor>>().len(), 1);
|
||||
assert_eq!(
|
||||
sc.attractors
|
||||
.iter()
|
||||
.filter(|a| a.dead == true)
|
||||
.collect::<Vec<&Attractor>>()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
|
||||
// TODO assert nodes 3,0 and 6,0
|
||||
assert_eq!(sc.nodes.len(), 3);
|
||||
assert_eq!(nodes.len(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user