Merge branch 'master' of gitlab.com:nationtech.io/nationtech.io

This commit is contained in:
Jean-Gabriel Gill-Couture 2024-02-27 23:40:15 -05:00
commit bc7b0f4f06
18 changed files with 1509 additions and 964 deletions

853
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@ leptos_actix = { version = "0.2", optional = true }
leptos_router = { version = "0.2", default-features = false } leptos_router = { version = "0.2", default-features = false }
log = "0.4" log = "0.4"
simple_logger = "4" simple_logger = "4"
wasm-bindgen = "=0.2.84" wasm-bindgen = "=0.2.87"
js-sys = "0.3.51" js-sys = "0.3.51"
rand = "0.8.5" rand = "0.8.5"
getrandom = { version = "0.2", features = ["js"] } getrandom = { version = "0.2", features = ["js"] }
@ -96,3 +96,7 @@ lib-features = ["hydrate"]
# #
# Optional. Defaults to false. # Optional. Defaults to false.
lib-default-features = false lib-default-features = false
[dev-dependencies]
test-log = "*"
env_logger = "*"

View File

@ -1,8 +1,9 @@
use crate::components::BackgroundProps;
use crate::components::Background; use crate::components::Background;
use crate::routes::whymdc::*; use crate::components::BackgroundProps;
use crate::routes::home::*;
use crate::routes::blog::*; use crate::routes::blog::*;
use crate::routes::empty::*;
use crate::routes::home::*;
use crate::routes::whymdc::*;
use leptos::*; use leptos::*;
use leptos_meta::*; use leptos_meta::*;
use leptos_router::*; use leptos_router::*;
@ -35,6 +36,7 @@ pub fn App(cx: Scope) -> impl IntoView {
<Route path="" view=|cx| view! { cx, <HomePage/> }/> <Route path="" view=|cx| view! { cx, <HomePage/> }/>
<Route path="/why-micro-datacenters" view=|cx| view! { cx, <WhyMicroDatacenters/> }/> <Route path="/why-micro-datacenters" view=|cx| view! { cx, <WhyMicroDatacenters/> }/>
<Route path="/blog" view=|cx| view! { cx, <Blog/> }/> <Route path="/blog" view=|cx| view! { cx, <Blog/> }/>
<Route path="/background" view=|cx| view! { cx, <Empty/> }/>
</Routes> </Routes>
</div> </div>
<Background class="fixed h-screen w-screen top-0 z-0"/> <Background class="fixed h-screen w-screen top-0 z-0"/>
@ -42,4 +44,3 @@ pub fn App(cx: Scope) -> impl IntoView {
</Router> </Router>
} }
} }

View File

@ -1,10 +1,15 @@
use std::cell::RefCell;
use std::rc::Rc;
use leptos::html::Canvas; use leptos::html::Canvas;
use leptos::*; use leptos::*;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
use web_sys::window; use web_sys::window;
use crate::space_colonization::Node; use crate::space_colonization::Node;
use crate::space_colonization::Point;
use crate::space_colonization::SpaceColonization; use crate::space_colonization::SpaceColonization;
#[component] #[component]
@ -16,16 +21,11 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let canvas_parent = canvas.parent_element().unwrap(); let canvas_parent = canvas.parent_element().unwrap();
let width = canvas_parent.client_width(); let width = canvas_parent.client_width();
let height = canvas_parent.client_height(); let height = canvas_parent.client_height();
canvas.set_width(u32::try_from(width).unwrap()); let window_width = u32::try_from(width).unwrap();
canvas.set_height(u32::try_from(height).unwrap()); let window_height = u32::try_from(height).unwrap();
let sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap()); canvas.set_width(window_width);
// TODO Resize on window resize canvas.set_height(window_height);
log!(
"TODO resize on window resize canvas parent size = {} {}",
canvas_parent.client_width(),
canvas_parent.client_height()
);
log!("in canvas");
let context = canvas let context = canvas
.get_context("2d") .get_context("2d")
.ok() .ok()
@ -34,42 +34,114 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
.unchecked_into::<web_sys::CanvasRenderingContext2d>(); .unchecked_into::<web_sys::CanvasRenderingContext2d>();
log!("context = {:#?}", context); log!("context = {:#?}", context);
context.set_stroke_style(&JsValue::from("white")); context.set_stroke_style(&JsValue::from("white"));
let context_to_render = context.clone();
// let render_node_fn : 'a Fn(&Node, &Node) = |n: &Node, child: &Node| {
let render_node_fn = move |n: &Node, child: &Node| {
context_to_render.move_to(n.position.x.into(), n.position.y.into());
context_to_render.line_to(child.position.x.into(), child.position.y.into());
};
let mut sc = SpaceColonization::new(
width.try_into().unwrap(),
height.try_into().unwrap(),
render_node_fn,
);
let nodes = Rc::new(RefCell::new(Vec::new()));
{
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 = {} {}",
canvas_parent.client_width(),
canvas_parent.client_height()
);
context.set_stroke_style(&JsValue::from("white"));
context.set_fill_style(&JsValue::from("yellow")); context.set_fill_style(&JsValue::from("yellow"));
log!("About to render nodes"); log!("About to render nodes");
let start_time = window().unwrap().performance().unwrap().now(); let start_time = window().unwrap().performance().unwrap().now();
for n in sc.nodes_tree.borrow().iter() {
context.fill_rect(n.position.x.into(), n.position.y.into(), 5.0, 5.0);
}
context.begin_path(); context.begin_path();
/*
let render_node_fn = |n: &Node, child: &Node| { let render_node_fn = |n: &Node, child: &Node| {
context.move_to(n.position.x.into(), n.position.y.into()); context.move_to(n.position.x.into(), n.position.y.into());
context.line_to(child.position.x.into(), child.position.y.into()); 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); // let render_id = window().unwrap().performance().unwrap().now();
// sc.render_all_nodes(&nodes.borrow(), render_id, render_node_fn);
context.stroke(); context.stroke();
context.set_fill_style(&JsValue::from("magenta"));
for a in sc.attractors.borrow().iter() {
context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0);
}
let end_time = window().unwrap().performance().unwrap().now(); let end_time = window().unwrap().performance().unwrap().now();
log!( log!(
"Rendering {} nodes and {} attractors took {}", "Rendering nodes and {} attractors took {}",
sc.nodes_tree.borrow().len(), sc.attractors.size(),
sc.attractors.borrow().len(),
end_time - start_time end_time - start_time
); );
let context = context.clone();
for _i in 1..150 { let closure = Closure::<dyn FnMut(_)>::new(move |_: web_sys::MouseEvent| {
sc.grow(); let start_time = window().unwrap().performance().unwrap().now();
let render_id = window().unwrap().performance().unwrap().now(); {
context.begin_path(); let mut nodesMut = nodes.borrow_mut();
sc.render_nodes(render_id, render_node_fn); sc.grow(&mut nodesMut);
context.stroke();
} }
// let render_id = window().unwrap().performance().unwrap().now();
// context.begin_path();
// sc.render_nodes(&nodes.borrow(), render_id, render_node_fn);
/*
context.set_fill_style(&JsValue::from("magenta"));
for a in sc.attractors.iter().filter(|a| a.dead) {
context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0);
}
context.set_fill_style(&JsValue::from("red"));
for a in sc.attractors.iter().filter(|a| !a.dead) {
context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0);
}
*/
context.stroke();
let end_time = window().unwrap().performance().unwrap().now();
log!(
"Rendering nodes and {} attractors took {}",
sc.attractors.size(),
end_time - start_time
);
});
let window = window().unwrap();
window
.set_interval_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
30,
)
.unwrap();
// window
// .add_event_listener_with_callback(
// "click",
// closure.as_ref().unchecked_ref(),
// )
// .unwrap();
closure.forget();
}); });
let class = format!("canvas {}", class); let class = format!("canvas {}", class);

View File

@ -1,6 +1,6 @@
mod codeblock;
mod code;
mod background; mod background;
pub use codeblock::*; mod code;
pub use code::*; mod codeblock;
pub use background::*; pub use background::*;
pub use code::*;
pub use codeblock::*;

View File

@ -1,7 +1,7 @@
mod space_colonization;
pub mod app; pub mod app;
mod components; mod components;
mod routes; mod routes;
mod space_colonization;
use cfg_if::cfg_if; use cfg_if::cfg_if;
cfg_if! { cfg_if! {

View File

@ -1,6 +1,6 @@
use crate::components::*;
use leptos::*; use leptos::*;
use leptos_meta::*; use leptos_meta::*;
use crate::components::*;
#[component] #[component]
pub fn Blog(cx: Scope) -> impl IntoView { pub fn Blog(cx: Scope) -> impl IntoView {

2
src/routes/empty/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod page;
pub use page::*;

12
src/routes/empty/page.rs Normal file
View File

@ -0,0 +1,12 @@
use leptos::*;
use leptos_meta::*;
/// Renders the home page of your application.
#[component]
pub fn Empty(cx: Scope) -> impl IntoView {
view! { cx,
<Title text="Pretty cool background | NationTech - Unconventional team for extraordinary challenges"/>
<div class="">
</div>
}
}

View File

@ -1,3 +1,4 @@
pub mod whymdc;
pub mod home;
pub mod blog; pub mod blog;
pub mod empty;
pub mod home;
pub mod whymdc;

View File

@ -31,5 +31,3 @@ pub fn SubsectionTitle(cx: Scope, dark: bool, children: Children) -> impl IntoVi
<h5 class={class}>{children(cx)}</h5> <h5 class={class}>{children(cx)}</h5>
} }
} }

View File

@ -1,25 +1,107 @@
use log::info;
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 { ) -> Option<Point> {
let node = &growth_cell.0; calculate_new_node_position_with_skip(node, attractors, segment_length, 0)
let attractors = &growth_cell.1; }
pub fn calculate_new_node_position_with_skip(
node: &Node,
attractors: &Vec<*mut Attractor>,
segment_length: u16,
attractors_to_skip: usize,
) -> Option<Point> {
assert!(
attractors.len() > 0,
"There must be at least one attractor!"
);
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().skip(attractors_to_skip) {
attraction_sum_x += a.position.x - node.position.x; if attractors_to_skip > 0 {
attraction_sum_y += a.position.y - node.position.y; info!("working on attractor {:?}", a);
}
let attractor: &Attractor;
unsafe {
attractor = &**a;
}
if attractors_to_skip > 0 {
info!("successfully worked with attractor {:?}", a);
}
attraction_sum_x += attractor.position.x - node.position.x;
attraction_sum_y += attractor.position.y - node.position.y;
}
// Here this fixes what happens when the sum of the vectors towards attractors is 0
// See test new_node_ignores_attractors_when_average_results_in_no_movement
if attraction_sum_y == 0 && attraction_sum_x == 0 {
if attractors.len() == 1 {
let a = attractors.first().unwrap();
unsafe {
(**a).dead = true;
}
return None;
}
assert!(
attractors.len() > attractors_to_skip + 1,
"There must be more attractors in the array than the number to skip"
);
return calculate_new_node_position_with_skip(
node,
attractors,
segment_length,
attractors_to_skip + 1,
);
} }
let point = Point { let point = Point {
x: node.position.x + attraction_sum_x / attractors.len() as i32, x: node.position.x + attraction_sum_x as i32,
y: node.position.y + attraction_sum_y / attractors.len() as i32, y: node.position.y + attraction_sum_y as i32,
}; };
node.position.movement(point, segment_length) let new_point = node.position.movement(point, segment_length);
println!("just did movement");
dbg!(node.position, point, segment_length, new_point);
let new_distance = new_point.distance(&node.position);
if new_distance > (segment_length * 2) as f64 {
info!(
"new node {:?} is far from parent {:?}",
new_point, node.position
);
let mut readable_attractors: Vec<&Attractor> = Vec::new();
unsafe {
attractors.iter().for_each(|a| {
readable_attractors.push(&**a);
});
}
info!(
"Calculations inputs are
attractors {:?}
node.position {:?}
segment_length {}
point {:?}",
readable_attractors, node.position, segment_length, point
);
info!(
"Calculations outputs are
distance {}
attraction_sum x {} , y {}
new_point {:?}",
new_distance, attraction_sum_x, attraction_sum_y, new_point
);
}
Some(new_point)
} }
#[cfg(test)] #[cfg(test)]
@ -29,13 +111,97 @@ 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 point = calculate_new_node_position(&growth_cell.as_refs(), SEGMENT_LENGTH); let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut();
let point = calculate_new_node_position(
&growth_cell.node,
&mut attractors_as_ptr_mut,
SEGMENT_LENGTH,
)
.unwrap();
assert_eq!(point, Point::new((0, 5))); assert_eq!(point, Point::new((0, 5)));
} }
#[test] #[test]
fn new_node_ignores_dead_attractor() {} fn new_node_ignores_attractors_when_average_results_in_no_movement() {
let mut growth_cell = GrowthCell::from_positions((10, 0), [(0, 0), (20, 0)].to_vec());
let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut();
let point = calculate_new_node_position(
&growth_cell.node,
&mut attractors_as_ptr_mut,
SEGMENT_LENGTH,
)
.unwrap();
assert_eq!(point, Point::new((15, 0)));
}
#[test]
fn new_node_ignores_dead_attractor() {
// TODO
assert!(false);
}
#[test_log::test]
fn large_number_of_attractors() {
let attractors = [
(1048, 241),
(935, 268),
(953, 363),
(978, 333),
(988, 355),
(894, 234),
(892, 331),
(1050, 275),
(972, 261),
(882, 280),
(1013, 212),
(1023, 233),
(899, 197),
(911, 285),
(879, 291),
(977, 293),
(938, 233),
(975, 196),
(964, 295),
(967, 197),
(869, 242),
(945, 323),
(1035, 287),
(962, 243),
(1023, 297),
(952, 185),
(984, 261),
(1011, 264),
(931, 209),
(900, 281),
(1006, 319),
(929, 295),
(936, 325),
(1001, 182),
(995, 225),
];
let node = (960, 266);
let mut growth_cell = GrowthCell::from_positions(node, attractors.to_vec());
let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut();
let point = calculate_new_node_position(
&growth_cell.node,
&mut attractors_as_ptr_mut,
SEGMENT_LENGTH,
)
.unwrap();
assert_eq!(point, Point::new((965, 266)));
/*
segment_length 5
Calculations outputs are
distance 996.1706681086329
attraction_sum x 17 , y 1
resulting point Point { x: 960, y: 266 }
*/
}
struct GrowthCell { struct GrowthCell {
node: Node, node: Node,
@ -43,14 +209,15 @@ mod tests {
} }
impl GrowthCell { impl GrowthCell {
pub fn from_positions(positions: Vec<(i32, i32)>) -> Self { pub fn from_positions(node: (i32, i32), attractors_positions: Vec<(i32, i32)>) -> Self {
assert!(positions.len() >= 2); assert!(attractors_positions.len() >= 1);
let node = Node { let node = Node {
position: Point::new(positions[0]), position: Point::new(node),
children: Vec::new().into(), children: Vec::new().into(),
growing: true,
}; };
let mut attractors = Vec::new(); let mut attractors = Vec::new();
for p in positions.iter().skip(1) { for p in attractors_positions.iter() {
attractors.push(Attractor { attractors.push(Attractor {
position: Point::new(*p), position: Point::new(*p),
dead: false, dead: false,
@ -59,8 +226,11 @@ mod tests {
Self { node, attractors } Self { node, attractors }
} }
fn as_refs(&self) -> (Node, Vec<&Attractor>) { fn attractors_as_ptr_mut(&mut self) -> Vec<*mut Attractor> {
(self.node, self.attractors.iter().collect()) self.attractors
.iter_mut()
.map(|a| a as *mut Attractor)
.collect()
} }
} }
} }

View File

@ -5,6 +5,7 @@ pub use point::*;
mod space_colonization; mod space_colonization;
pub use space_colonization::*; pub use space_colonization::*;
mod math; mod math;
mod spatial_index;
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
@ -12,16 +13,16 @@ extern "C" {
fn performance() -> web_sys::Performance; fn performance() -> web_sys::Performance;
} }
#[derive(Debug)] #[derive(Debug, PartialEq, Eq, Hash)]
pub struct Attractor { pub struct Attractor {
pub position: Point, pub position: Point,
pub dead: bool, pub dead: bool,
} }
impl Attractor { impl Attractor {
pub fn new(position: (i32, i32)) -> Attractor { pub fn new(position: Point) -> Attractor {
Attractor { Attractor {
position: Point::new(position), position,
dead: false, dead: false,
} }
} }
@ -38,18 +39,21 @@ impl Attractor {
/// - Probably worth marking nodes as dead when no attractor is in range /// - Probably worth marking nodes as dead when no attractor is in range
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Node { pub struct Node {
/// When a Node is born it is growing
/// it stops growing when there is no attractor in range
pub growing: bool,
pub position: Point, pub position: Point,
pub children: Vec<Node>, pub children: Vec<Node>,
} }
#[derive(Debug)] pub struct Attraction {
pub struct NodeRef { node: *mut Node,
path: Vec<u16> distance: f64,
}
impl Attraction {
fn new(node: &mut Node, distance: f64) -> Attraction {
Self { node, distance }
} }
#[derive(Debug)]
pub struct AttractorRef {
path: Vec<u16>
} }
impl std::hash::Hash for Node { impl std::hash::Hash for Node {
@ -59,23 +63,23 @@ impl std::hash::Hash for Node {
} }
impl Node { impl Node {
pub fn render<F>(&self, render_id: f64, render_fn: F) pub fn render<G>(&self, render_id: f64, render_fn: G)
where where
F: Copy + Fn(&Node, &Node), G: Copy + Fn(&Node, &Node),
{ {
let children = self.children; for child in self.children.iter() {
for child in children.iter() {
render_fn(self, &child); render_fn(self, &child);
} }
for child in children.iter() { for child in self.children.iter() {
child.render(render_id, render_fn); child.render(render_id, render_fn);
} }
} }
fn new(position: Point) -> Self { pub fn new(position: Point) -> Self {
Self { Self {
position, position,
growing: true,
children: Vec::new().into(), children: Vec::new().into(),
} }
} }

View File

@ -69,24 +69,15 @@ mod tests {
#[test] #[test]
fn distance_to_itself_is_zero() { fn distance_to_itself_is_zero() {
let p = Point { let p = Point { x: 1, y: 1 };
x: 1,
y: 1,
};
assert_eq!(p.distance(&p), 0.0); assert_eq!(p.distance(&p), 0.0);
} }
#[test] #[test]
fn distance_same_x_is_y() { fn distance_same_x_is_y() {
let p1 = Point { let p1 = Point { x: 1, y: 1 };
x: 1,
y: 1,
};
let p2 = Point { let p2 = Point { x: 1, y: 5 };
x: 1,
y: 5,
};
assert_eq!(p1.distance(&p2), 4.0); assert_eq!(p1.distance(&p2), 4.0);
} }
@ -122,60 +113,36 @@ mod tests {
#[test] #[test]
fn distance_quadrant3() { fn distance_quadrant3() {
let p1 = Point { let p1 = Point { x: 3, y: 4 };
x: 3,
y: 4,
};
let p2 = Point { let p2 = Point { x: 0, y: 0 };
x: 0,
y: 0,
};
assert_eq!(p1.distance(&p2), 5.0); assert_eq!(p1.distance(&p2), 5.0);
} }
#[test] #[test]
fn distance_quadrant2() { fn distance_quadrant2() {
let p1 = Point { let p1 = Point { x: 3, y: 4 };
x: 3,
y: 4,
};
let p2 = Point { let p2 = Point { x: 0, y: 100 };
x: 0,
y: 100,
};
assert_eq!(p1.distance(&p2) as f32, 96.04687); assert_eq!(p1.distance(&p2) as f32, 96.04687);
} }
#[test] #[test]
fn distance_quadrant2_fast() { fn distance_quadrant2_fast() {
let p1 = Point { let p1 = Point { x: 3, y: 4 };
x: 3,
y: 4,
};
let p2 = Point { let p2 = Point { x: 3, y: 50 };
x: 3,
y: 50,
};
assert_eq!(p1.distance(&p2), 46.0); assert_eq!(p1.distance(&p2), 46.0);
} }
#[test] #[test]
fn distance_quadrant4() { fn distance_quadrant4() {
let p1 = Point { let p1 = Point { x: 3, y: 4 };
x: 3,
y: 4,
};
let p2 = Point { let p2 = Point { x: 50, y: -50 };
x: 50,
y: -50,
};
assert_eq!(p1.distance(&p2) as f32, 71.5891); assert_eq!(p1.distance(&p2) as f32, 71.5891);
} }
@ -191,7 +158,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]
@ -234,4 +201,3 @@ mod tests {
assert_eq!(root.movement(node, 5), Point::new((10, 45))); assert_eq!(root.movement(node, 5), Point::new((10, 45)));
} }
} }

View File

@ -1,14 +1,19 @@
use super::math::calculate_new_node_position; use super::math::calculate_new_node_position;
use super::{Attractor, Node, NodeRef, Point}; use super::spatial_index::SpatialIndex;
use super::Attraction;
use super::{Attractor, Node, Point};
use log::info; use log::info;
use rand::thread_rng; use rand::thread_rng;
use rand::Rng; use rand::Rng;
use std::collections::HashMap; use std::collections::HashMap;
pub struct SpaceColonization { pub struct SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
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.
/// ///
@ -40,34 +45,32 @@ pub struct SpaceColonization {
/// ///
/// If density is 10, then there will be an average distance of 10 between attractors /// If density is 10, then there will be an average distance of 10 between attractors
density: i32, density: i32,
new_nodes: Vec<(NodeRef, Node)>, pub attractors: SpatialIndex<Attractor>,
/// Flat list of all nodes in the tree render_fn: F,
/// [node, child1, grand-child, child2, grand-child2]
nodes: Vec<Node>,
pub attractors: Vec<Attractor>,
} }
impl SpaceColonization { impl<F> SpaceColonization<F>
pub fn new(width: i32, height: i32) -> SpaceColonization { where
let mut nodes_vec = Vec::new(); F: Fn(&Node, &Node),
nodes_vec.push(Node { {
position: Point { x: 100, y: 100 }, pub fn new(width: i32, height: i32, render_fn: F) -> SpaceColonization<F>
children: Vec::new(), 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 { let mut sc = SpaceColonization {
max_point: Point { max_point: Point {
x: width, x: width,
y: height, y: height,
}, },
kill_distance: 5, kill_distance: 5.0,
attraction_distance: 100, attraction_distance,
segment_length: 5, segment_length: 5,
density: 30, density: 30,
nodes: nodes_vec,
attractors, attractors,
new_nodes: Vec::new(), render_fn,
}; };
sc.place_attractors(); sc.place_attractors();
@ -79,30 +82,32 @@ impl SpaceColonization {
pub fn new_for_tests( pub fn new_for_tests(
width: i32, width: i32,
height: i32, height: i32,
nodes: Vec<Node>, attractors: SpatialIndex<Attractor>,
attractors: Vec<Attractor>, render_fn: F,
) -> SpaceColonization { ) -> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
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,
nodes,
attractors, attractors,
new_nodes: Vec::new(), render_fn,
} }
} }
pub fn render_nodes<F>(&self, render_id: f64, render_fn: F) pub fn render_all_nodes<G>(&self, nodes: &Vec<Node>, render_id: f64, render_fn: G)
where where
F: Copy + Fn(&Node, &Node), G: Copy + Fn(&Node, &Node),
{ {
info!("Rendering {} nodes", self.nodes.len()); info!("Rendering {} nodes", nodes.len());
for n in self.nodes.iter() { for n in nodes.iter() {
n.render(render_id, render_fn); n.render(render_id, render_fn);
} }
} }
@ -112,10 +117,8 @@ impl SpaceColonization {
let mut y_pos = 0; let mut y_pos = 0;
while x_pos < self.max_point.x { while x_pos < self.max_point.x {
while y_pos < self.max_point.y { while y_pos < self.max_point.y {
self.attractors.push(Attractor { let point = self.get_random_point(x_pos.into(), y_pos.into());
position: self.get_random_point(x_pos.into(), y_pos.into()), self.attractors.add(&point, Attractor::new(point));
dead: false,
});
y_pos += self.density; y_pos += self.density;
} }
x_pos += self.density; x_pos += self.density;
@ -147,75 +150,127 @@ impl SpaceColonization {
} }
} }
pub fn grow(&self) { pub fn grow<'b>(&mut self, mut nodes: &'b mut Vec<Node>) {
// TODO self.grow_nodes(&mut nodes)
// Find a clean API that will be stable across refactoring
// Write the test against this api including performance
// Find a way to make a compile-time safe datastructure that will ensure that
// - I can store my attractors and their state (remove them or update them when dead)
// - I can store my nodes and their state
// - I can efficiently render my nodes on a canvas
// - I use as little memory as possible
// - I can update my nodes
self.grow_nodes();
println!("new nodes for iteration {:?}", self.new_nodes);
let mut nodes_mut = self.nodes;
for new_pair in self.new_nodes.iter() {
new_pair.0.children.push(new_pair.1);
nodes_mut.push(new_pair.1);
}
self.new_nodes.clear();
} }
pub fn grow_nodes(&self) { 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 attractors = self.attractors;
let mut growing_paths: HashMap<<NodeRef>, Vec<AttractorRef>> = HashMap::new();
for a in attractors.iter() {
if a.dead {
continue;
}
let mut closest_node: Option<Node> = None;
let mut closest_node_distance = f64::MAX;
for n in self.nodes.iter() { // ------------ START OF BLOCK ----------
let distance = n.position.distance(&a.position); // DO NOT MODIFY THE NODES VEC AFTER THIS
if distance <= self.attraction_distance as f64 { // We are taking raw pointers to Node to be dereferenced later, if the Vec of nodes is
// TODO make sure it is closest node amongs all nodes // modified it will cause wrong behavior or segmentation faults and crash
if distance < closest_node_distance { let mut attractor_to_closest_node: HashMap<*mut Attractor, Attraction> = HashMap::new();
closest_node = Some(n);
closest_node_distance = distance; for n in nodes.iter_mut() {
if distance < self.kill_distance as f64 { self.build_attractor_to_closest_node(&mut attractor_to_closest_node, n);
a.dead.replace(true); }
let mut node_to_attractors: HashMap<*mut Node, Vec<*mut Attractor>> = HashMap::new();
attractor_to_closest_node
.drain()
.for_each(|(attractor, attraction)| {
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
//
// LEARNING : using unsafe here sounded like not the worst idea and was a nice
// opportunity to learn, which did happen. I am closing in now and I can say that I
// have learned quite a lot about how rust programs work and should be designed.
unsafe {
let new_point =
calculate_new_node_position(&(**node), attractor, self.segment_length);
if let Some(new_point) = new_point {
let new_node = Node::new(new_point);
attractor.iter().for_each(|a| {
if (**a).position.distance(&new_node.position) <= self.kill_distance {
dead_attractors.push(*a);
(**a).dead = true;
}
});
(self.render_fn)(&**node, &new_node);
(**node).children.push(new_node);
} }
} }
});
} }
fn build_attractor_to_closest_node<'b>(
&'b mut self,
mut attractor_to_closest_node: &mut HashMap<*mut Attractor, Attraction>,
n: &mut Node,
) {
for child in n.children.iter_mut() {
self.build_attractor_to_closest_node(&mut attractor_to_closest_node, child);
}
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));
} }
if let Some(node) = closest_node {
if let Some(attractors) = growing_paths.get_mut(&node) {
attractors.push(a);
} else { } else {
let mut attractors = Vec::new(); attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1));
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); fn find_attractors_in_range(&mut self, n: &Node) -> Vec<(*mut Attractor, f64)> {
for a in growth_cell.1 { let mut attractors_in_range = Vec::new();
if position.distance(&a.position) < self.kill_distance as f64 { for a in self
a.dead.replace(true); .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, distance));
} }
} }
self.new_nodes attractors_in_range
.push((growth_cell.0, Node::new(position)));
}
} }
} }
@ -225,57 +280,114 @@ mod test {
use super::*; use super::*;
fn assert_nodes(sc: &SpaceColonization, expected_nodes: Vec<(Point, Point)>) { fn assert_vertices<'a, F>(
sc: &SpaceColonization<F>,
nodes: &'a Vec<Node>,
mut expected_nodes: Vec<(Point, Point)>,
) where
F: Copy + Fn(&Node, &Node),
{
let rendered_nodes = RefCell::new(Vec::new()); let rendered_nodes = RefCell::new(Vec::new());
sc.render_nodes(0.0, |n1, n2| { sc.render_all_nodes(&nodes, 0.0, |n1: &Node, n2: &Node| {
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]
fn grow_should_reach_single_attractor_and_die() { fn grow_should_reach_single_attractor_and_die() {
let mut nodes = Vec::new(); let mut nodes = Vec::new();
nodes.push(Node::new(Point::new((0, 0)))); nodes.push(Node::new(Point::new((0, 0))));
let mut attractors = Vec::new(); let mut attractors = SpatialIndex::new(Point::new((100, 100)), 10);
attractors.push(Attractor::new((10, 0))); let point = Point::new((10, 0));
attractors.add(&point, Attractor::new(point));
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_eq!(
assert_eq!(sc.attractors.len(), 1); sc.attractors
assert!(sc .get_surrounding_elements_with_filter(&point, |_| true)
.attractors .len(),
.iter() 1
.find(|a| a.dead == true) );
.is_none());
sc.grow(); println!("before grow");
dbg!(&nodes);
sc.grow(&mut nodes);
println!("after grow 1");
dbg!(&nodes);
assert_eq!(sc.new_nodes.len(), 0); assert_eq!(
assert!(sc sc.attractors
.attractors .get_surrounding_elements_with_filter(&point, |a| a.dead == false)
.iter() .len(),
.find(|a| a.dead == true) 1
.is_none()); );
// TODO assert point 3,0
sc.grow();
assert_eq!(sc sc.grow(&mut nodes);
.attractors println!("after grow 2");
.iter() dbg!(&nodes);
.filter(|a| a.dead == true)
.collect::<Vec<&Attractor>>().len(), 1);
// TODO assert nodes 3,0 and 6,0 assert_vertices(
assert_eq!(sc.nodes.len(), 3); &sc,
&nodes,
Vec::from([
(Point::new((0, 0)), Point::new((3, 0))),
(Point::new((3, 0)), Point::new((6, 0))),
]),
);
assert_eq!(
sc.attractors
.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
);
}
#[test]
fn grow_should_ignore_dead_attractors() {
let mut nodes = Vec::new();
nodes.push(Node::new(Point::new((0, 0))));
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
.get_surrounding_elements_with_filter(&Point::new((10, 0)), |a| a.dead == true)
.len(),
1
);
sc.grow(&mut nodes);
assert_vertices(&sc, &nodes, Vec::new());
} }
} }

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

@ -0,0 +1,307 @@
use super::Point;
#[derive(Debug)]
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,
}
impl<T> SpatialIndex<T>
where
T: std::fmt::Debug,
{
pub fn new(max_point: Point, cell_size: i32) -> SpatialIndex<T> {
// Here we add 1 to number cells x and y to handle the right and bottom edges where
// x = max_point.x or y = max_point.y
// For example, with max_point of (100,100)
// the point 100,100 is valid but 100/10 = 10, which is out of bounds of a zero based
// array of length 10. So we add 1 to the length and 10 is a valid index in a zero based
// array of length 11
let number_cells_x = (max_point.x / cell_size) + 1;
let number_cells = number_cells_x * ((max_point.y / cell_size) + 1);
let mut elements = Vec::with_capacity(number_cells as usize);
for _ in 0..number_cells {
elements.push(Vec::new());
}
SpatialIndex {
elements,
cell_size,
number_cells_x,
size: 0,
max_point,
}
}
/// Adds an element in the cell of point
///
/// panics if the point is outside of the grid
pub fn add(&mut self, point: &Point, element: T) {
let element_index = self.get_index_from_position(point);
self.elements
.get_mut(element_index as usize)
.unwrap()
.push(element);
self.size += 1;
}
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<*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);
}
surrounding_elements
}
fn get_index_from_position(&self, point: &Point) -> usize {
((point.x / self.cell_size) + ((point.y / self.cell_size) * self.number_cells_x)) as usize
}
fn get_surrounding_indices(&self, point: &Point) -> Vec<usize> {
let element_index = self.get_index_from_position(point);
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
if point.x >= self.cell_size {
indices.push(element_index - row_offset - 1);
}
// top middle
indices.push(element_index - row_offset);
// top right
if point.x < last_cell_x_start {
indices.push(element_index - row_offset + 1);
}
}
// middle left
if point.x >= self.cell_size {
indices.push(element_index - 1);
}
// middle middle can be skipped, already added
// middle right
if point.x < last_cell_x_start {
indices.push(element_index + 1);
}
if point.y < last_row_y_min {
// bottom left
if point.x >= self.cell_size {
indices.push(element_index + row_offset - 1);
}
// bottom middle
indices.push(element_index + row_offset);
// bottom right
if point.x < last_cell_x_start {
indices.push(element_index + row_offset + 1);
}
}
indices
}
}
#[cfg(test)]
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 mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
assert_eq!(
index.get_surrounding_elements_with_filter(&Point::new((0, 0)), |_| true),
Vec::<*mut usize>::new()
);
}
#[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_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 50)), |_| true),
vec![132],
);
}
#[test]
fn multiple_points_in_every_surrounding_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((40, 40)), 4040);
index.add(&Point::new((45, 45)), 4040);
index.add(&Point::new((50, 40)), 5040);
index.add(&Point::new((60, 40)), 6040);
index.add(&Point::new((40, 50)), 4050);
index.add(&Point::new((50, 50)), 5050);
index.add(&Point::new((60, 50)), 6050);
index.add(&Point::new((50, 50)), 5050);
index.add(&Point::new((51, 51)), 5151);
index.add(&Point::new((60, 60)), 6060);
index.add(&Point::new((40, 60)), 4060);
index.add(&Point::new((50, 60)), 5060);
index.add(&Point::new((60, 60)), 6060);
assert_eq!(
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,
]
.sort()
);
}
#[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_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 9)), |_| true),
vec![132],
);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 0)), |_| true),
vec![132],
);
}
#[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_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 95)), |_| true),
vec![132],
);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 100)), |_| true),
vec![132],
);
}
#[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_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((95, 50)), |_| true),
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_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((9, 50)), |_| true),
vec![132],
);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((0, 50)), |_| true),
vec![132],
);
}
#[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_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]
);
}
}