300 lines
9.9 KiB
Rust
300 lines
9.9 KiB
Rust
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::{Cell, RefCell};
|
|
use std::collections::HashMap;
|
|
use std::rc::Rc;
|
|
|
|
pub struct SpaceColonization {
|
|
max_point: Point,
|
|
/// When a node grows within kill_distance of an attractor, the attractor is killed
|
|
kill_distance: i32,
|
|
/// Maximum distance between an attractor and a node for the node to
|
|
/// be affected by the attractor.
|
|
///
|
|
/// 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: 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,
|
|
// TODO learning opportunity : avoid Rc and RefCell to have memory contiguous, increase
|
|
// performance and simplify code. Using a mutable struct instead of pointers to nodeswill
|
|
// improve performance. If required, use ids to point to nodes, those ids can be simply their
|
|
// indices in the array. If the node pointed to is deep, the index can be a vec or a tuple.
|
|
new_nodes: RefCell<Vec<(Rc<Node>, 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 mut nodes_vec = Vec::new();
|
|
nodes_vec.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 {
|
|
max_point: Point {
|
|
x: width,
|
|
y: height,
|
|
},
|
|
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()),
|
|
};
|
|
|
|
sc.place_attractors();
|
|
|
|
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),
|
|
{
|
|
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 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: Cell::new(false),
|
|
});
|
|
y_pos += self.density;
|
|
}
|
|
x_pos += self.density;
|
|
y_pos = 0;
|
|
}
|
|
}
|
|
|
|
fn get_random_point(&self, x_pos: i32, y_pos: i32) -> Point {
|
|
let half_density: i32 = (self.density / 2).into();
|
|
let mut x_min = x_pos - half_density;
|
|
if x_min < 0 {
|
|
x_min = 0;
|
|
}
|
|
|
|
let mut y_min = y_pos - half_density;
|
|
if y_min < 0 {
|
|
y_min = 0;
|
|
}
|
|
|
|
Point {
|
|
x: thread_rng()
|
|
.gen_range(x_min..x_pos + half_density)
|
|
.try_into()
|
|
.unwrap(),
|
|
y: thread_rng()
|
|
.gen_range(y_min..y_pos + half_density)
|
|
.try_into()
|
|
.unwrap(),
|
|
}
|
|
}
|
|
|
|
pub fn grow(&self) {
|
|
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) {
|
|
// 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 attractors = self.attractors.borrow();
|
|
let mut growing_paths: HashMap<Rc<Node>, Vec<&Attractor>> = HashMap::new();
|
|
for a in attractors.iter() {
|
|
if a.dead.get() {
|
|
continue;
|
|
}
|
|
let mut closest_node: Option<Rc<Node>> = None;
|
|
let mut closest_node_distance = f64::MAX;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let Some(node) = closest_node {
|
|
if let Some(attractors) = growing_paths.get_mut(&node) {
|
|
attractors.push(a);
|
|
} else {
|
|
let mut attractors = Vec::new();
|
|
attractors.push(a);
|
|
growing_paths.insert(node, 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))));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
}
|