refactoring works, only left to add a test and feature of ignoring dead attractors and nodes
This commit is contained in:
parent
66e1f813cf
commit
e67f97058b
@ -1,17 +1,18 @@
|
|||||||
use super::{Attractor, Node, Point};
|
use super::{Attractor, Node, Point};
|
||||||
|
|
||||||
pub fn calculate_new_node_position(
|
pub fn calculate_new_node_position(
|
||||||
growth_cell: &(Node, Vec<&Attractor>),
|
node: &Node,
|
||||||
|
attractors: &Vec<*mut Attractor>,
|
||||||
segment_length: u16,
|
segment_length: u16,
|
||||||
) -> Point {
|
) -> Point {
|
||||||
let node = &growth_cell.0;
|
|
||||||
let attractors = &growth_cell.1;
|
|
||||||
let mut attraction_sum_x = 0;
|
let mut attraction_sum_x = 0;
|
||||||
let mut attraction_sum_y = 0;
|
let mut attraction_sum_y = 0;
|
||||||
|
|
||||||
for a in attractors.iter() {
|
for a in attractors.iter() {
|
||||||
attraction_sum_x += a.position.x - node.position.x;
|
unsafe {
|
||||||
attraction_sum_y += a.position.y - node.position.y;
|
attraction_sum_x += (**a).position.x - node.position.x;
|
||||||
|
attraction_sum_y += (**a).position.y - node.position.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let point = Point {
|
let point = Point {
|
||||||
@ -29,9 +30,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_node_moves_toward_single_attractor() {
|
fn new_node_moves_toward_single_attractor() {
|
||||||
let growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec());
|
let mut growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec());
|
||||||
|
let attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut();
|
||||||
let point = calculate_new_node_position(&(growth_cell.node, growth_cell.attractors.iter().collect()), SEGMENT_LENGTH);
|
|
||||||
|
let point = calculate_new_node_position(
|
||||||
|
&growth_cell.node,
|
||||||
|
&attractors_as_ptr_mut,
|
||||||
|
SEGMENT_LENGTH,
|
||||||
|
);
|
||||||
assert_eq!(point, Point::new((0, 5)));
|
assert_eq!(point, Point::new((0, 5)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,5 +66,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
Self { node, attractors }
|
Self { node, attractors }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn attractors_as_ptr_mut(&mut self) -> Vec<*mut Attractor> {
|
||||||
|
self.attractors.iter_mut().map(|a| { a as *mut Attractor }).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,14 +45,24 @@ pub struct Node {
|
|||||||
pub children: Vec<Node>,
|
pub children: Vec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Attraction {
|
||||||
|
node: *mut Node,
|
||||||
|
distance: f64,
|
||||||
|
}
|
||||||
|
impl Attraction {
|
||||||
|
fn new(node: &mut Node, distance: f64) -> Attraction {
|
||||||
|
Self { node, distance }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NodeRef {
|
pub struct NodeRef {
|
||||||
path: Vec<u16>
|
path: Vec<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AttractorRef {
|
pub struct AttractorRef {
|
||||||
path: Vec<u16>
|
path: Vec<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::hash::Hash for Node {
|
impl std::hash::Hash for Node {
|
||||||
|
|||||||
@ -191,7 +191,7 @@ mod tests {
|
|||||||
fn movement_does_not_overlap() {
|
fn movement_does_not_overlap() {
|
||||||
let root = Point::new((0,1));
|
let root = Point::new((0,1));
|
||||||
let node = Point::new((0,0));
|
let node = Point::new((0,0));
|
||||||
assert_eq!(root.movement(node.clone(), 2), node);
|
assert_eq!(root.movement(node.clone(), 2), Point::new((0, -1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
use super::math::calculate_new_node_position;
|
||||||
|
use super::Attraction;
|
||||||
use super::{Attractor, Node, Point};
|
use super::{Attractor, Node, Point};
|
||||||
use log::info;
|
use log::info;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
@ -7,7 +9,7 @@ use std::collections::HashMap;
|
|||||||
pub struct SpaceColonization {
|
pub struct SpaceColonization {
|
||||||
max_point: Point,
|
max_point: Point,
|
||||||
/// When a node grows within kill_distance of an attractor, the attractor is killed
|
/// When a node grows within kill_distance of an attractor, the attractor is killed
|
||||||
kill_distance: i32,
|
kill_distance: f64,
|
||||||
/// Maximum distance between an attractor and a node for the node to
|
/// Maximum distance between an attractor and a node for the node to
|
||||||
/// be affected by the attractor.
|
/// be affected by the attractor.
|
||||||
///
|
///
|
||||||
@ -51,7 +53,7 @@ impl SpaceColonization {
|
|||||||
x: width,
|
x: width,
|
||||||
y: height,
|
y: height,
|
||||||
},
|
},
|
||||||
kill_distance: 5,
|
kill_distance: 5.0,
|
||||||
attraction_distance: 100,
|
attraction_distance: 100,
|
||||||
segment_length: 5,
|
segment_length: 5,
|
||||||
density: 30,
|
density: 30,
|
||||||
@ -64,17 +66,13 @@ impl SpaceColonization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn new_for_tests(
|
pub fn new_for_tests(width: i32, height: i32, attractors: Vec<Attractor>) -> SpaceColonization {
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
attractors: Vec<Attractor>,
|
|
||||||
) -> SpaceColonization {
|
|
||||||
SpaceColonization {
|
SpaceColonization {
|
||||||
max_point: Point {
|
max_point: Point {
|
||||||
x: width,
|
x: width,
|
||||||
y: height,
|
y: height,
|
||||||
},
|
},
|
||||||
kill_distance: 5,
|
kill_distance: 5.0,
|
||||||
attraction_distance: 12,
|
attraction_distance: 12,
|
||||||
segment_length: 3,
|
segment_length: 3,
|
||||||
density: 3,
|
density: 3,
|
||||||
@ -132,7 +130,7 @@ impl SpaceColonization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grow(&mut self, nodes: Vec<Node>) -> Vec<Node> {
|
pub fn grow(&mut self, mut nodes: Vec<Node>) -> Vec<Node> {
|
||||||
// TODO
|
// TODO
|
||||||
// [x] Find a clean API that will be stable across refactoring
|
// [x] Find a clean API that will be stable across refactoring
|
||||||
// [ ] Write the test against this api including performance
|
// [ ] Write the test against this api including performance
|
||||||
@ -142,51 +140,112 @@ impl SpaceColonization {
|
|||||||
// - I can efficiently render my nodes on a canvas
|
// - I can efficiently render my nodes on a canvas
|
||||||
// - I use as little memory as possible
|
// - I use as little memory as possible
|
||||||
// - I can update my nodes
|
// - I can update my nodes
|
||||||
self.grow_nodes(nodes)
|
self.grow_nodes(&mut nodes);
|
||||||
|
nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grow_nodes(&mut self, nodes: Vec<Node>) -> Vec<Node>{
|
pub fn grow_nodes(&mut self, nodes: &mut Vec<Node>) {
|
||||||
// iterate through attractors
|
// iterate through attractors
|
||||||
// find closest node within attraction range
|
// find closest node within attraction range
|
||||||
// build a map of nodes to affecting attractors
|
// build a map of nodes to affecting attractors
|
||||||
// attractors within the attraction range that this node is the closest to
|
// attractors within the attraction range that this node is the closest to
|
||||||
//
|
//
|
||||||
// calculate new node position
|
// calculate new node position
|
||||||
let mut influence_map: HashMap<&Attractor, (*mut Node, f64)> = HashMap::new();
|
|
||||||
|
|
||||||
let nodes = nodes.into_iter().map(|mut n| {
|
// ------------ START OF BLOCK ----------
|
||||||
if !n.growing {
|
// DO NOT MODIFY THE NODES VEC AFTER THIS
|
||||||
return n;
|
// We are taking raw pointers to Node to be dereferenced later, if the Vec of nodes is
|
||||||
}
|
// modified it will cause wrong behavior or segmentation faults and crash
|
||||||
|
let mut attractor_to_closest_node: HashMap<*mut Attractor, Attraction> = HashMap::new();
|
||||||
|
|
||||||
let attractors_in_range = self.find_attractors_in_range(&n);
|
for n in nodes.iter_mut() {
|
||||||
|
self.build_attractor_to_closest_node(&mut attractor_to_closest_node, n);
|
||||||
|
}
|
||||||
|
|
||||||
if attractors_in_range.is_empty() {
|
let mut node_to_attractors: HashMap<*mut Node, Vec<*mut Attractor>> = HashMap::new();
|
||||||
n.growing = false;
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
for a in attractors_in_range {
|
attractor_to_closest_node
|
||||||
if let Some(closest) = influence_map.get(a.0) {
|
.drain()
|
||||||
if a.1 < closest.1 {
|
.for_each(|(attractor, attraction)| {
|
||||||
influence_map.insert(a.0, (&mut n, a.1));
|
let mut node_attractors = match node_to_attractors.remove(&attraction.node) {
|
||||||
|
Some(node_a) => node_a,
|
||||||
|
None => Vec::new(),
|
||||||
|
};
|
||||||
|
node_attractors.push(attractor);
|
||||||
|
node_to_attractors.insert(attraction.node, node_attractors);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut dead_attractors: Vec<*mut Attractor> = Vec::new();
|
||||||
|
node_to_attractors.iter().for_each(|(node, attractor)| {
|
||||||
|
// Unsafe is used here for two main reasons :
|
||||||
|
//
|
||||||
|
// PERFORMANCE : Using unsafe here allows to store multiple mutable references to a
|
||||||
|
// Node and save a few bytes of memory and cpu cycles to store a struct that holds
|
||||||
|
// a fake reference to the Node, such as the path in the tree, and then resolve it
|
||||||
|
// handling the Option<> every step of the way.
|
||||||
|
//
|
||||||
|
// Using a raw pointer we can Oh I actually just realised having a raw pointer deep
|
||||||
|
// in a tree of Vec that are getting pushed into might very well cause the pointer
|
||||||
|
// to become invalid when its parent gets pushed into and moved to another memory
|
||||||
|
// space
|
||||||
|
//
|
||||||
|
// Using raw fixed length arrays would solve that but its a fine line between too
|
||||||
|
// large memory usage and enough children nodes
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let new_node = Node::new(calculate_new_node_position(
|
||||||
|
&(**node),
|
||||||
|
attractor,
|
||||||
|
self.segment_length,
|
||||||
|
));
|
||||||
|
attractor.iter().for_each(|a| {
|
||||||
|
if (**a).position.distance(&new_node.position) <= self.kill_distance {
|
||||||
|
dead_attractors.push(*a);
|
||||||
|
(**a).dead = true;
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
influence_map.insert(a.0, (&mut n, a.1));
|
(**node).children.push(new_node);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
n
|
});
|
||||||
}).collect();
|
|
||||||
|
|
||||||
nodes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_attractors_in_range(&self, n: &Node) -> Vec<(&Attractor, f64)> {
|
fn build_attractor_to_closest_node<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
mut attractor_to_closest_node: &mut HashMap<*mut Attractor, Attraction>,
|
||||||
|
n: &mut Node,
|
||||||
|
) {
|
||||||
|
if !n.growing {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attractors_in_range = self.find_attractors_in_range(&n);
|
||||||
|
|
||||||
|
if attractors_in_range.is_empty() {
|
||||||
|
n.growing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for a in attractors_in_range {
|
||||||
|
if let Some(closest) = attractor_to_closest_node.get(&a.0) {
|
||||||
|
if a.1 < closest.distance {
|
||||||
|
attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in n.children.iter_mut() {
|
||||||
|
self.build_attractor_to_closest_node(&mut attractor_to_closest_node, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_attractors_in_range(&mut self, n: &Node) -> Vec<(*mut Attractor, f64)> {
|
||||||
let mut attractors_in_range = Vec::new();
|
let mut attractors_in_range = Vec::new();
|
||||||
for a in self.attractors.iter() {
|
for a in self.attractors.iter_mut() {
|
||||||
let distance = n.position.distance(&a.position);
|
let distance = n.position.distance(&a.position);
|
||||||
if distance < self.attraction_distance as f64 {
|
if distance < self.attraction_distance as f64 {
|
||||||
attractors_in_range.push((a, distance));
|
attractors_in_range.push((a as *mut Attractor, distance));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attractors_in_range
|
attractors_in_range
|
||||||
@ -199,19 +258,28 @@ mod test {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn assert_nodes(sc: &SpaceColonization, nodes: &Vec<Node>, expected_nodes: Vec<(Point, Point)>) {
|
fn assert_vertices(
|
||||||
|
sc: &SpaceColonization,
|
||||||
|
nodes: &Vec<Node>,
|
||||||
|
mut expected_nodes: Vec<(Point, Point)>,
|
||||||
|
) {
|
||||||
let rendered_nodes = RefCell::new(Vec::new());
|
let rendered_nodes = RefCell::new(Vec::new());
|
||||||
sc.render_nodes(&nodes, 0.0, |n1, n2| {
|
sc.render_nodes(&nodes, 0.0, |n1, n2| {
|
||||||
rendered_nodes.borrow_mut().push((n1.position, n2.position));
|
rendered_nodes.borrow_mut().push((n1.position, n2.position));
|
||||||
});
|
});
|
||||||
|
let sort_points = |line1: &(Point, Point), line2: &(Point, Point)| {
|
||||||
rendered_nodes.borrow_mut().sort_by(|line1, line2| {
|
|
||||||
if line1.0 != line2.0 {
|
if line1.0 != line2.0 {
|
||||||
return line1.0.cmp(&line2.0);
|
return line1.0.cmp(&line2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return line1.1.cmp(&line2.1);
|
return line1.1.cmp(&line2.1);
|
||||||
});
|
};
|
||||||
|
rendered_nodes.borrow_mut().sort_by(sort_points);
|
||||||
|
expected_nodes.sort_by(sort_points);
|
||||||
|
|
||||||
|
let rendered_nodes = rendered_nodes.take();
|
||||||
|
|
||||||
|
assert_eq!(rendered_nodes, expected_nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -223,16 +291,30 @@ mod test {
|
|||||||
|
|
||||||
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors);
|
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors);
|
||||||
|
|
||||||
assert_nodes(&sc, &nodes, Vec::from([(Point::new((0, 0)), Point::new((10, 0)))]));
|
|
||||||
assert_eq!(sc.attractors.len(), 1);
|
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());
|
||||||
|
|
||||||
|
println!("before grow");
|
||||||
|
dbg!(&nodes);
|
||||||
nodes = sc.grow(nodes);
|
nodes = sc.grow(nodes);
|
||||||
|
println!("after grow 1");
|
||||||
|
dbg!(&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
|
|
||||||
|
|
||||||
nodes = sc.grow(nodes);
|
nodes = sc.grow(nodes);
|
||||||
|
println!("after grow 2");
|
||||||
|
dbg!(&nodes);
|
||||||
|
|
||||||
|
// TODO assert nodes 3,0 and 6,0
|
||||||
|
assert_vertices(
|
||||||
|
&sc,
|
||||||
|
&nodes,
|
||||||
|
Vec::from([
|
||||||
|
(Point::new((0, 0)), Point::new((3, 0))),
|
||||||
|
(Point::new((3, 0)), Point::new((6, 0))),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sc.attractors
|
sc.attractors
|
||||||
@ -242,8 +324,5 @@ mod test {
|
|||||||
.len(),
|
.len(),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO assert nodes 3,0 and 6,0
|
|
||||||
assert_eq!(nodes.len(), 3);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user