feat(space_colonization): Spatial index plugged in, performance is much better

This commit is contained in:
jeangab 2023-08-21 09:13:43 -04:00
parent 63c13e1739
commit a5a093d7d6
5 changed files with 223 additions and 308 deletions

View File

@ -46,26 +46,29 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
render_node_fn,
);
let nodes = Rc::new(RefCell::new(Vec::new()));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width / 3) as i32,
(window_height / 3) as i32,
))));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width / 2) as i32,
(window_height / 3) as i32,
))));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width / 3) as i32,
(window_height / 2) as i32,
))));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width - 200) as i32,
(window_height / 3) as i32,
))));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width - 100) as i32,
(window_height - 100) as i32,
))));
{
let mut nodesMut = nodes.borrow_mut();
nodesMut.push(Node::new(Point::new((
(window_width / 3) as i32,
(window_height / 3) as i32,
))));
nodesMut.push(Node::new(Point::new((
(window_width / 2) as i32,
(window_height / 3) as i32,
))));
nodesMut.push(Node::new(Point::new((
(window_width / 3) as i32,
(window_height / 2) as i32,
))));
nodesMut.push(Node::new(Point::new((
(window_width - 200) as i32,
(window_height / 3) as i32,
))));
nodesMut.push(Node::new(Point::new((
(window_width - 100) as i32,
(window_height - 100) as i32,
))));
}
// TODO Resize on window resize
log!(
"TODO resize on window resize canvas parent size = {} {}",
@ -91,13 +94,16 @@ 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.attractors.len(),
sc.attractors.size(),
end_time - start_time
);
let context = context.clone();
let closure = Closure::<dyn FnMut(_)>::new(move |_: web_sys::MouseEvent| {
let start_time = window().unwrap().performance().unwrap().now();
sc.grow(&mut nodes.borrow_mut());
{
let mut nodesMut = nodes.borrow_mut();
sc.grow(&mut nodesMut);
}
// let render_id = window().unwrap().performance().unwrap().now();
// context.begin_path();
// sc.render_nodes(&nodes.borrow(), render_id, render_node_fn);
@ -116,7 +122,7 @@ 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.attractors.len(),
sc.attractors.size(),
end_time - start_time
);
});
@ -124,10 +130,17 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let window = window().unwrap();
window
.set_interval_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
10,
)
.unwrap();
closure.as_ref().unchecked_ref(),
30,
)
.unwrap();
// window
// .add_event_listener_with_callback(
// "click",
// closure.as_ref().unchecked_ref(),
// )
// .unwrap();
closure.forget();
});

View File

@ -5,7 +5,7 @@ pub use point::*;
mod space_colonization;
pub use space_colonization::*;
mod math;
mod spacial_index;
mod spatial_index;
#[wasm_bindgen]
extern "C" {

View File

@ -1,4 +1,5 @@
use super::math::calculate_new_node_position;
use super::spatial_index::SpatialIndex;
use super::Attraction;
use super::{Attractor, Node, Point};
use log::info;
@ -44,7 +45,7 @@ where
///
/// If density is 10, then there will be an average distance of 10 between attractors
density: i32,
pub attractors: Vec<Attractor>,
pub attractors: SpatialIndex<Attractor>,
render_fn: F,
}
@ -56,7 +57,8 @@ where
where
F: Fn(&Node, &Node),
{
let attractors = Vec::new();
let attraction_distance = 100;
let attractors = SpatialIndex::new(Point::new((width, height)), attraction_distance);
let mut sc = SpaceColonization {
max_point: Point {
@ -64,7 +66,7 @@ where
y: height,
},
kill_distance: 5.0,
attraction_distance: 100,
attraction_distance,
segment_length: 5,
density: 30,
attractors,
@ -80,7 +82,7 @@ where
pub fn new_for_tests(
width: i32,
height: i32,
attractors: Vec<Attractor>,
attractors: SpatialIndex<Attractor>,
render_fn: F,
) -> SpaceColonization<F>
where
@ -115,9 +117,8 @@ where
let mut y_pos = 0;
while x_pos < self.max_point.x {
while y_pos < self.max_point.y {
self.attractors.push(Attractor::new(
self.get_random_point(x_pos.into(), y_pos.into()),
));
let point = self.get_random_point(x_pos.into(), y_pos.into());
self.attractors.add(&point, Attractor::new(point));
y_pos += self.density;
}
x_pos += self.density;
@ -150,7 +151,6 @@ where
}
pub fn grow<'b>(&mut self, mut nodes: &'b mut Vec<Node>) {
info!("Growing nodes {:?}", nodes);
self.grow_nodes(&mut nodes)
}
@ -207,11 +207,6 @@ where
// have learned quite a lot about how rust programs work and should be designed.
unsafe {
/*
let segfault_boy: *const u8 = usize::MAX as *const u8;
let memory_content = *segfault_boy;
println!("memory content at address 0 {}", memory_content);
*/
let new_point =
calculate_new_node_position(&(**node), attractor, self.segment_length);
if let Some(new_point) = new_point {
@ -262,10 +257,17 @@ where
fn find_attractors_in_range(&mut self, n: &Node) -> Vec<(*mut Attractor, f64)> {
let mut attractors_in_range = Vec::new();
for a in self.attractors.iter_mut().filter(|a| !a.dead) {
let distance = n.position.distance(&a.position);
for a in self
.attractors
.get_surrounding_elements_with_filter(&n.position, |a| !a.dead)
.iter()
{
let distance;
unsafe {
distance = n.position.distance(&(**a).position);
}
if distance < self.attraction_distance as f64 {
attractors_in_range.push((a as *mut Attractor, distance));
attractors_in_range.push((*a, distance));
}
}
attractors_in_range
@ -308,13 +310,18 @@ mod test {
fn grow_should_reach_single_attractor_and_die() {
let mut nodes = Vec::new();
nodes.push(Node::new(Point::new((0, 0))));
let mut attractors = Vec::new();
attractors.push(Attractor::new(Point::new((10, 0))));
let mut attractors = SpatialIndex::new(Point::new((100, 100)), 10);
let point = Point::new((10, 0));
attractors.add(&point, Attractor::new(point));
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors, |_, _| {});
assert_eq!(sc.attractors.len(), 1);
assert!(sc.attractors.iter().find(|a| a.dead == true).is_none());
assert_eq!(
sc.attractors
.get_surrounding_elements_with_filter(&point, |_| true)
.len(),
1
);
println!("before grow");
dbg!(&nodes);
@ -322,7 +329,13 @@ mod test {
println!("after grow 1");
dbg!(&nodes);
assert!(sc.attractors.iter().find(|a| a.dead == true).is_none());
assert_eq!(
sc.attractors
.get_surrounding_elements_with_filter(&point, |a| a.dead == false)
.len(),
1
);
sc.grow(&mut nodes);
println!("after grow 2");
@ -336,12 +349,15 @@ mod test {
(Point::new((3, 0)), Point::new((6, 0))),
]),
);
assert_eq!(
sc.attractors
.iter()
.filter(|a| a.dead == true)
.collect::<Vec<&Attractor>>()
.get_surrounding_elements_with_filter(&point, |a| a.dead == false)
.len(),
0
);
assert_eq!(
sc.attractors
.get_surrounding_elements_with_filter(&point, |a| a.dead == true)
.len(),
1
);
@ -351,16 +367,24 @@ mod test {
fn grow_should_ignore_dead_attractors() {
let mut nodes = Vec::new();
nodes.push(Node::new(Point::new((0, 0))));
let mut attractors = Vec::new();
attractors.push(Attractor {
position: Point::new((10, 0)),
dead: true,
});
let mut attractors = SpatialIndex::new(Point::new((100, 100)), 10);
let point = Point::new((10, 0));
attractors.add(
&point,
Attractor {
position: point,
dead: true,
},
);
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors, |_, _| {});
assert_eq!(sc.attractors.len(), 1);
assert!(sc.attractors.iter().find(|a| a.dead == true).is_some());
assert_eq!(
sc.attractors
.get_surrounding_elements_with_filter(&Point::new((10, 0)), |a| a.dead == true)
.len(),
1
);
sc.grow(&mut nodes);

View File

@ -1,201 +0,0 @@
use std::sync::RwLock;
use super::{Attractor, Node, Point};
use rand::thread_rng;
use rand::Rng;
use web_sys::console;
use web_sys::window;
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 sqrt((density)**2 + (density)**2)
attraction_distance: i32,
segment_length: i32,
/// 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,
pub root_nodes: Vec<RwLock<Node>>,
pub attractors: Vec<RwLock<Attractor>>,
}
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
let mut root_nodes = Vec::new();
root_nodes.push(RwLock::new(Node {
position: Point { x: 100, y: 100 },
children: Vec::new(),
}));
let attractors = Vec::new();
let mut sc = SpaceColonization {
max_point: Point {
x: width,
y: height,
},
kill_distance: 10,
attraction_distance: 43,
segment_length: 5,
density: 30,
root_nodes,
attractors,
};
sc.place_attractors();
return sc;
}
pub fn render_nodes<F>(&self, render_id: f64, render_fn: F)
where
F: Copy + Fn(&Node, &Node),
{
for n in self.root_nodes.iter() {
n.read().unwrap().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.push(RwLock::new(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()),
dead: 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 {
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) {
for n in self.root_nodes.iter() {
self.grow_node(n);
}
// iterate through the list of nodes that are not dead yet (still had an active attractor
// previous iteration)
// For each node :
// find attractors within attraction distance of node
// calculate distance to affecting attractors
// determine how many new nodes grow from here
// determine position of new nodes
// remove current node from leaves list
}
fn grow_node(&self, n: &RwLock<Node>) {
n.read()
.unwrap()
.children
.iter()
.for_each(|n| self.grow_node(n));
let affecting_attractors = self.find_affecting_attractors(&n);
console::log_1(
&format!("Found {} affecting attractors", affecting_attractors.len()).into(),
);
let new_node = self.create_new_node(n, &affecting_attractors);
for a in affecting_attractors {
let mut a = a.write().unwrap();
if n.read().unwrap().position.distance(&a.position) < self.kill_distance as f64 {
a.dead = true;
}
}
console::log_1(&format!("New node {:?}", new_node).into());
n.write().unwrap().children.push(RwLock::new(new_node));
}
fn find_affecting_attractors(&self, n: &RwLock<Node>) -> Vec<&RwLock<Attractor>> {
let mut affecting = Vec::new();
for a in self.attractors.iter() {
let aread = a.read().unwrap();
// TODO remove attractors instead of marking them dead
// used to display them at the moment but could also be moved
// to a dead queue
if aread.dead {
continue;
}
let n_distance = n.read().unwrap().position.distance(&aread.position);
// todo for some reason I cannot verify closest node to attractor here
if n_distance < self.attraction_distance.into() && self.is_closest_node(&n.read().unwrap(), &aread) {
affecting.push(a)
}
}
affecting
}
fn create_new_node(
&self,
n: &RwLock<Node>,
affecting_attractors: &Vec<&RwLock<Attractor>>,
) -> Node {
let n = n.read().unwrap();
let mut attraction_sum_x = 0;
let mut attraction_sum_y = 0;
for a in affecting_attractors.iter() {
attraction_sum_x += n.position.x - a.read().unwrap().position.x;
attraction_sum_y += n.position.y - a.read().unwrap().position.y;
}
Node {
position: Point {
x: n.position.x + attraction_sum_x / affecting_attractors.len() as i32,
y: n.position.y + attraction_sum_y / affecting_attractors.len() as i32,
},
children: Vec::new(),
}
}
fn is_closest_node(&self, node: &Node, a: &Attractor) -> bool {
let node_distance = node.position.distance(&a.position);
for n in self.root_nodes.iter() {
let n_read = match n.read() {
Ok(val) => val,
Err(e) => todo!("Cannot read node {}", e),
};
if n_read.position.distance(&a.position) < node_distance {
return false;
}
// todo iterate the entire tree
todo!();
}
return true;
}
}

View File

@ -1,13 +1,14 @@
use super::Point;
#[derive(Debug)]
struct SpatialIndex<T> {
pub struct SpatialIndex<T> {
elements: Vec<Vec<T>>,
/// The number of cells between a cell and the cell right below it.
///
/// For a table of 100x100 with cell_size = 10, number_cells_x is 11 (100/10+1)
number_cells_x: i32,
cell_size: i32,
size: u32,
max_point: Point,
}
@ -32,6 +33,7 @@ where
elements,
cell_size,
number_cells_x,
size: 0,
max_point,
}
}
@ -45,18 +47,45 @@ where
.get_mut(element_index as usize)
.unwrap()
.push(element);
self.size += 1;
}
pub fn get_surrounding_elements<'a>(&'a self, point: &Point) -> Vec<&'a T> {
pub fn size(&self) -> u32 {
self.size
}
/// Fetches elements in the cell corresponding to point and all the adjacent cells
/// filtering returned elements with filter
///
/// May panic or return wrong values if the point is outside this SpatialIndex max point
pub fn get_surrounding_elements_with_filter<'a, F>(
self: &'a mut Self,
point: &Point,
mut filter: F,
) -> Vec<*mut T>
where
F: FnMut(&T) -> bool,
{
let surrounding_indices: Vec<usize> = self.get_surrounding_indices(point);
let mut surrounding_elements = Vec::new();
dbg!(&surrounding_indices);
for i in surrounding_indices {
surrounding_elements.extend(self.elements.get(i).unwrap().iter());
let mut surrounding_elements: Vec<*mut T> = Vec::new();
for i in surrounding_indices.iter() {
println!("elements len {}", self.elements.len());
let elements_for_index = self
.elements
.get_mut(*i as usize)
.expect(
format!(
"point {:?} is in range for spatial_index with max_point {:?}, currently getting index {}, among surrounding indices {:?}",
point, self.max_point, i, surrounding_indices
)
.as_str(),
);
let elements_for_index = elements_for_index
.iter_mut()
.filter(|el| filter(el))
.map(|el| el as *mut T);
surrounding_elements.extend(elements_for_index);
}
dbg!(&self.elements[115]);
dbg!(&surrounding_elements);
dbg!(&self.elements.len());
surrounding_elements
}
@ -69,6 +98,9 @@ where
let mut indices = Vec::from([element_index]);
let row_offset = self.number_cells_x as usize;
let last_cell_x_start = self.max_point.x - (self.max_point.x % self.cell_size);
let last_row_y_min = self.max_point.y - (self.max_point.y % self.cell_size);
// top row
if point.y >= self.cell_size {
// top left
@ -78,7 +110,7 @@ where
// top middle
indices.push(element_index - row_offset);
// top right
if point.x < self.max_point.x {
if point.x < last_cell_x_start {
indices.push(element_index - row_offset + 1);
}
}
@ -90,11 +122,11 @@ where
// middle middle can be skipped, already added
// middle right
if point.x < self.max_point.x {
if point.x < last_cell_x_start {
indices.push(element_index + 1);
}
if point.y < self.max_point.y {
if point.y < last_row_y_min {
// bottom left
if point.x >= self.cell_size {
indices.push(element_index + row_offset - 1);
@ -103,7 +135,7 @@ where
// bottom middle
indices.push(element_index + row_offset);
// bottom right
if point.x < self.max_point.x {
if point.x < last_cell_x_start {
indices.push(element_index + row_offset + 1);
}
}
@ -116,12 +148,21 @@ where
mod test {
use super::*;
fn assert_vec_values(actual: Vec<*mut usize>, expected: Vec<usize>) {
let resolved_actual: Vec<usize>;
unsafe {
resolved_actual = actual.iter().map(|n| **n).collect();
}
assert_eq!(resolved_actual, expected);
}
#[test]
fn when_no_element_surrounding_nodes_returns_empty_vec() {
let index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
assert_eq!(
index.get_surrounding_elements(&Point::new((0, 0))),
Vec::<&usize>::new()
index.get_surrounding_elements_with_filter(&Point::new((0, 0)), |_| true),
Vec::<*mut usize>::new()
);
}
@ -129,9 +170,9 @@ mod test {
fn added_point_is_surrounding_itself() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((50, 50)), 132);
assert_eq!(
index.get_surrounding_elements(&Point::new((50, 50))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 50)), |_| true),
vec![132],
);
}
@ -152,7 +193,9 @@ mod test {
index.add(&Point::new((50, 60)), 5060);
index.add(&Point::new((60, 60)), 6060);
assert_eq!(
index.get_surrounding_elements(&Point::new((50, 50))).sort(),
index
.get_surrounding_elements_with_filter(&Point::new((50, 50)), |_| true)
.sort(),
vec![
&4040, &4545, &5040, &6040, &4050, &5050, &6050, &5050, &5151, &6060, &4060, &5060,
&6060,
@ -165,13 +208,13 @@ mod test {
fn point_on_top_edge_is_close_to_first_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((50, 0)), 132);
assert_eq!(
index.get_surrounding_elements(&Point::new((50, 9))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 9)), |_| true),
vec![132],
);
assert_eq!(
index.get_surrounding_elements(&Point::new((50, 0))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 0)), |_| true),
vec![132],
);
}
@ -179,13 +222,13 @@ mod test {
fn point_on_bottom_edge_is_close_to_bottom_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((50, 100)), 132);
assert_eq!(
index.get_surrounding_elements(&Point::new((50, 95))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 95)), |_| true),
vec![132],
);
assert_eq!(
index.get_surrounding_elements(&Point::new((50, 100))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 100)), |_| true),
vec![132],
);
}
@ -193,26 +236,26 @@ mod test {
fn point_on_right_edge_is_close_to_first_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((100, 50)), 132);
assert_eq!(
index.get_surrounding_elements(&Point::new((95, 50))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((95, 50)), |_| true),
vec![132],
);
assert_eq!(
index.get_surrounding_elements(&Point::new((100, 50))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((100, 50)), |_| true),
vec![132],
);
}
#[test]
fn point_on_left_edge_is_close_to_first_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((0, 50)), 132);
assert_eq!(
index.get_surrounding_elements(&Point::new((9, 50))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((9, 50)), |_| true),
vec![132],
);
assert_eq!(
index.get_surrounding_elements(&Point::new((0, 50))),
vec![&132]
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((0, 50)), |_| true),
vec![132],
);
}
@ -220,9 +263,45 @@ mod test {
fn when_elements_too_far_surrounding_is_empty() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((0, 0)), 132);
assert_eq!(
index.get_surrounding_elements(&Point::new((99, 99))),
Vec::<&usize>::new()
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((99, 99)), |_| true),
Vec::<usize>::new(),
);
}
#[test]
fn works_when_max_point_is_not_multiple_of_cell_size() {
let cell_size = 100;
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((1920, 580)), cell_size);
for x_cell in 0..19 as usize {
for y_cell in 0..5 as usize {
index.add(
&Point::new((x_cell as i32 * cell_size, y_cell as i32 * cell_size)),
x_cell * 100 + y_cell,
);
}
}
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((1833, 502)), |_| true),
vec![1905, 1904, 1805, 1804]
);
}
#[test]
fn works_when_max_point_is_not_multiple_of_cell_size() {
let cell_size = 100;
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((1920, 580)), cell_size);
for x_cell in 0..19 as usize {
for y_cell in 0..5 as usize {
index.add(
&Point::new((x_cell as i32 * cell_size, y_cell as i32 * cell_size)),
x_cell * 100 + y_cell,
);
}
}
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((1833, 502)), |_| true),
vec![1905, 1904, 1805, 1804]
);
}
}