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 }
log = "0.4"
simple_logger = "4"
wasm-bindgen = "=0.2.84"
wasm-bindgen = "=0.2.87"
js-sys = "0.3.51"
rand = "0.8.5"
getrandom = { version = "0.2", features = ["js"] }
@ -96,3 +96,7 @@ lib-features = ["hydrate"]
#
# Optional. Defaults to 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::routes::whymdc::*;
use crate::routes::home::*;
use crate::components::BackgroundProps;
use crate::routes::blog::*;
use crate::routes::empty::*;
use crate::routes::home::*;
use crate::routes::whymdc::*;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
@ -35,6 +36,7 @@ pub fn App(cx: Scope) -> impl IntoView {
<Route path="" view=|cx| view! { cx, <HomePage/> }/>
<Route path="/why-micro-datacenters" view=|cx| view! { cx, <WhyMicroDatacenters/> }/>
<Route path="/blog" view=|cx| view! { cx, <Blog/> }/>
<Route path="/background" view=|cx| view! { cx, <Empty/> }/>
</Routes>
</div>
<Background class="fixed h-screen w-screen top-0 z-0"/>
@ -42,4 +44,3 @@ pub fn App(cx: Scope) -> impl IntoView {
</Router>
}
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use crate::components::*;
use leptos::*;
use leptos_meta::*;
use crate::components::*;
#[component]
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 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>
}
}

View File

@ -394,7 +394,7 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
</p>
<p class="text-justify mt-2 text-neutral-400 text-xs ml-72 mr-72">
"https://www.pewresearch.org/internet/2019/11/15/americans-and-privacy-concerned-confused-and-feeling-lack-of-control-over-their-personal-information/?fbcli
d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</p>
</div>

View File

@ -1,25 +1,107 @@
use log::info;
use super::{Attractor, Node, Point};
pub fn calculate_new_node_position(
growth_cell: &(Node, Vec<&Attractor>),
node: &Node,
attractors: &Vec<*mut Attractor>,
segment_length: u16,
) -> Point {
let node = &growth_cell.0;
let attractors = &growth_cell.1;
) -> Option<Point> {
calculate_new_node_position_with_skip(node, attractors, segment_length, 0)
}
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_y = 0;
for a in attractors.iter() {
attraction_sum_x += a.position.x - node.position.x;
attraction_sum_y += a.position.y - node.position.y;
for a in attractors.iter().skip(attractors_to_skip) {
if attractors_to_skip > 0 {
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 {
x: node.position.x + attraction_sum_x / attractors.len() as i32,
y: node.position.y + attraction_sum_y / attractors.len() as i32,
x: node.position.x + attraction_sum_x 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)]
@ -29,13 +111,97 @@ mod tests {
#[test]
fn new_node_moves_toward_single_attractor() {
let growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec());
let point = calculate_new_node_position(&growth_cell.as_refs(), SEGMENT_LENGTH);
let mut growth_cell = GrowthCell::from_positions((0, 0), [(0, 10)].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((0, 5)));
}
#[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 {
node: Node,
@ -43,14 +209,15 @@ mod tests {
}
impl GrowthCell {
pub fn from_positions(positions: Vec<(i32, i32)>) -> Self {
assert!(positions.len() >= 2);
pub fn from_positions(node: (i32, i32), attractors_positions: Vec<(i32, i32)>) -> Self {
assert!(attractors_positions.len() >= 1);
let node = Node {
position: Point::new(positions[0]),
position: Point::new(node),
children: Vec::new().into(),
growing: true,
};
let mut attractors = Vec::new();
for p in positions.iter().skip(1) {
for p in attractors_positions.iter() {
attractors.push(Attractor {
position: Point::new(*p),
dead: false,
@ -59,8 +226,11 @@ mod tests {
Self { node, attractors }
}
fn as_refs(&self) -> (Node, Vec<&Attractor>) {
(self.node, self.attractors.iter().collect())
fn attractors_as_ptr_mut(&mut self) -> Vec<*mut Attractor> {
self.attractors
.iter_mut()
.map(|a| a as *mut Attractor)
.collect()
}
}
}

View File

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

View File

@ -69,169 +69,135 @@ mod tests {
#[test]
fn distance_to_itself_is_zero() {
let p = Point {
x: 1,
y: 1,
};
let p = Point { x: 1, y: 1 };
assert_eq!(p.distance(&p), 0.0);
}
#[test]
fn distance_same_x_is_y() {
let p1 = Point {
x: 1,
y: 1,
};
let p1 = Point { x: 1, y: 1 };
let p2 = Point {
x: 1,
y: 5,
};
let p2 = Point { x: 1, y: 5 };
assert_eq!(p1.distance(&p2), 4.0);
}
#[test]
fn distance_same_y_is_x() {
let p1 = Point::new((1,1));
let p2 = Point::new((5,1));
let p1 = Point::new((1, 1));
let p2 = Point::new((5, 1));
assert_eq!(p1.distance(&p2), 4.0);
}
#[test]
fn distance_3_4_5() {
let p1 = Point::new((0,0));
let p2 = Point::new((3,4));
let p1 = Point::new((0, 0));
let p2 = Point::new((3, 4));
assert_eq!(p1.distance(&p2), 5.0);
}
#[test]
fn distance_is_always_positive() {
let p1 = Point::new((10,10));
let p2 = Point::new((5,10));
let p1 = Point::new((10, 10));
let p2 = Point::new((5, 10));
assert_eq!(p1.distance(&p2), 5.0);
let p1 = Point::new((10,10));
let p2 = Point::new((10,5));
let p1 = Point::new((10, 10));
let p2 = Point::new((10, 5));
assert_eq!(p1.distance(&p2), 5.0);
}
#[test]
fn distance_quadrant3() {
let p1 = Point {
x: 3,
y: 4,
};
let p1 = Point { x: 3, y: 4 };
let p2 = Point {
x: 0,
y: 0,
};
let p2 = Point { x: 0, y: 0 };
assert_eq!(p1.distance(&p2), 5.0);
}
#[test]
fn distance_quadrant2() {
let p1 = Point {
x: 3,
y: 4,
};
let p1 = Point { x: 3, y: 4 };
let p2 = Point {
x: 0,
y: 100,
};
let p2 = Point { x: 0, y: 100 };
assert_eq!(p1.distance(&p2) as f32, 96.04687);
}
#[test]
fn distance_quadrant2_fast() {
let p1 = Point {
x: 3,
y: 4,
};
let p1 = Point { x: 3, y: 4 };
let p2 = Point {
x: 3,
y: 50,
};
let p2 = Point { x: 3, y: 50 };
assert_eq!(p1.distance(&p2), 46.0);
}
#[test]
fn distance_quadrant4() {
let p1 = Point {
x: 3,
y: 4,
};
let p1 = Point { x: 3, y: 4 };
let p2 = Point {
x: 50,
y: -50,
};
let p2 = Point { x: 50, y: -50 };
assert_eq!(p1.distance(&p2) as f32, 71.5891);
}
#[test]
fn movement_does_nothing_when_right_length() {
let root = Point::new((0,0));
let node = Point::new((0,1));
let root = Point::new((0, 0));
let node = Point::new((0, 1));
assert_eq!(root.movement(node.clone(), 1), node);
}
#[test]
fn movement_does_not_overlap() {
let root = Point::new((0,1));
let node = Point::new((0,0));
assert_eq!(root.movement(node.clone(), 2), node);
let root = Point::new((0, 1));
let node = Point::new((0, 0));
assert_eq!(root.movement(node.clone(), 2), Point::new((0, -1)));
}
#[test]
fn movement_adjusts_to_asked_length() {
let root = Point::new((0,0));
let node = Point::new((0,1));
assert_eq!(root.movement(node, 10), Point::new((0,10)));
let root = Point::new((0, 0));
let node = Point::new((0, 1));
assert_eq!(root.movement(node, 10), Point::new((0, 10)));
}
#[test]
fn movement_works_away_from_origin() {
let root = Point::new((10,10));
let node = Point::new((10,11));
assert_eq!(root.movement(node, 10), Point::new((10,20)));
let root = Point::new((10, 10));
let node = Point::new((10, 11));
assert_eq!(root.movement(node, 10), Point::new((10, 20)));
}
#[test]
fn movement_works_in_two_dimension() {
let root = Point::new((10,10));
let node = Point::new((40,50));
assert_eq!(root.movement(node, 50), Point::new((40,50)));
let root = Point::new((10, 10));
let node = Point::new((40, 50));
assert_eq!(root.movement(node, 50), Point::new((40, 50)));
let root = Point::new((10,10));
let node = Point::new((40,50));
assert_eq!(root.movement(node, 5), Point::new((13,14)));
let root = Point::new((10, 10));
let node = Point::new((40, 50));
assert_eq!(root.movement(node, 5), Point::new((13, 14)));
}
#[test]
fn movement_works_in_all_directions() {
let root = Point::new((40,50));
let node = Point::new((10,10));
assert_eq!(root.movement(node, 5), Point::new((37,46)));
let root = Point::new((40, 50));
let node = Point::new((10, 10));
assert_eq!(root.movement(node, 5), Point::new((37, 46)));
let root = Point::new((50,10));
let node = Point::new((10,10));
assert_eq!(root.movement(node, 5), Point::new((45,10)));
let root = Point::new((50, 10));
let node = Point::new((10, 10));
assert_eq!(root.movement(node, 5), Point::new((45, 10)));
let root = Point::new((10,50));
let node = Point::new((10,10));
assert_eq!(root.movement(node, 5), Point::new((10,45)));
let root = Point::new((10, 50));
let node = Point::new((10, 10));
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::{Attractor, Node, NodeRef, Point};
use super::spatial_index::SpatialIndex;
use super::Attraction;
use super::{Attractor, Node, Point};
use log::info;
use rand::thread_rng;
use rand::Rng;
use std::collections::HashMap;
pub struct SpaceColonization {
pub struct SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
max_point: Point,
/// 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
/// 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
density: i32,
new_nodes: Vec<(NodeRef, Node)>,
/// Flat list of all nodes in the tree
/// [node, child1, grand-child, child2, grand-child2]
nodes: Vec<Node>,
pub attractors: Vec<Attractor>,
pub attractors: SpatialIndex<Attractor>,
render_fn: F,
}
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
let mut nodes_vec = Vec::new();
nodes_vec.push(Node {
position: Point { x: 100, y: 100 },
children: Vec::new(),
});
let attractors = Vec::new();
impl<F> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
pub fn new(width: i32, height: i32, render_fn: F) -> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
let attraction_distance = 100;
let attractors = SpatialIndex::new(Point::new((width, height)), attraction_distance);
let mut sc = SpaceColonization {
max_point: Point {
x: width,
y: height,
},
kill_distance: 5,
attraction_distance: 100,
kill_distance: 5.0,
attraction_distance,
segment_length: 5,
density: 30,
nodes: nodes_vec,
attractors,
new_nodes: Vec::new(),
render_fn,
};
sc.place_attractors();
@ -79,30 +82,32 @@ impl SpaceColonization {
pub fn new_for_tests(
width: i32,
height: i32,
nodes: Vec<Node>,
attractors: Vec<Attractor>,
) -> SpaceColonization {
attractors: SpatialIndex<Attractor>,
render_fn: F,
) -> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
SpaceColonization {
max_point: Point {
x: width,
y: height,
},
kill_distance: 5,
kill_distance: 5.0,
attraction_distance: 12,
segment_length: 3,
density: 3,
nodes,
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
F: Copy + Fn(&Node, &Node),
G: Copy + Fn(&Node, &Node),
{
info!("Rendering {} nodes", self.nodes.len());
for n in self.nodes.iter() {
info!("Rendering {} nodes", nodes.len());
for n in nodes.iter() {
n.render(render_id, render_fn);
}
}
@ -112,10 +117,8 @@ impl SpaceColonization {
let mut y_pos = 0;
while x_pos < self.max_point.x {
while y_pos < self.max_point.y {
self.attractors.push(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()),
dead: false,
});
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;
@ -147,75 +150,127 @@ impl SpaceColonization {
}
}
pub fn grow(&self) {
// TODO
// 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<'b>(&mut self, mut nodes: &'b mut Vec<Node>) {
self.grow_nodes(&mut nodes)
}
pub fn grow_nodes(&self) {
pub fn grow_nodes(&mut self, nodes: &mut Vec<Node>) {
// iterate through attractors
// find closest node within attraction range
// build a map of nodes to affecting attractors
// attractors within the attraction range that this node is the closest to
//
// calculate new node position
let 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() {
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);
closest_node_distance = distance;
if distance < self.kill_distance as f64 {
a.dead.replace(true);
// ------------ START OF BLOCK ----------
// DO NOT MODIFY THE NODES VEC AFTER THIS
// 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();
for n in nodes.iter_mut() {
self.build_attractor_to_closest_node(&mut attractor_to_closest_node, n);
}
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 {
let mut attractors = Vec::new();
attractors.push(a);
growing_paths.insert(node, attractors);
attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1));
}
}
}
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);
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
.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
.push((growth_cell.0, Node::new(position)));
}
attractors_in_range
}
}
@ -225,57 +280,114 @@ mod test {
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());
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().sort_by(|line1, line2| {
let sort_points = |line1: &(Point, Point), line2: &(Point, Point)| {
if line1.0 != line2.0 {
return line1.0.cmp(&line2.0);
}
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]
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((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 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!(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
);
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!(sc
.attractors
.iter()
.find(|a| a.dead == true)
.is_none());
// TODO assert point 3,0
assert_eq!(
sc.attractors
.get_surrounding_elements_with_filter(&point, |a| a.dead == false)
.len(),
1
);
sc.grow();
assert_eq!(sc
.attractors
.iter()
.filter(|a| a.dead == true)
.collect::<Vec<&Attractor>>().len(), 1);
sc.grow(&mut nodes);
println!("after grow 2");
dbg!(&nodes);
// TODO assert nodes 3,0 and 6,0
assert_eq!(sc.nodes.len(), 3);
assert_vertices(
&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]
);
}
}