Merge branch 'master' of gitlab.com:nationtech.io/nationtech.io
This commit is contained in:
commit
bc7b0f4f06
853
Cargo.lock
generated
853
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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 = "*"
|
||||
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
);
|
||||
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);
|
||||
|
||||
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.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);
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
mod space_colonization;
|
||||
pub mod app;
|
||||
mod components;
|
||||
mod routes;
|
||||
mod space_colonization;
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
|
||||
@ -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
2
src/routes/empty/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod page;
|
||||
pub use page::*;
|
||||
12
src/routes/empty/page.rs
Normal file
12
src/routes/empty/page.rs
Normal 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>
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod whymdc;
|
||||
pub mod home;
|
||||
pub mod blog;
|
||||
pub mod empty;
|
||||
pub mod home;
|
||||
pub mod whymdc;
|
||||
|
||||
@ -31,5 +31,3 @@ pub fn SubsectionTitle(cx: Scope, dark: bool, children: Children) -> impl IntoVi
|
||||
<h5 class={class}>{children(cx)}</h5>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,20 +46,20 @@ impl Point {
|
||||
|
||||
impl Ord for Point {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
if self.x > other.x {
|
||||
return std::cmp::Ordering::Greater;
|
||||
}
|
||||
if self.x < other.x {
|
||||
return std::cmp::Ordering::Less;
|
||||
}
|
||||
if self.y > other.y {
|
||||
return std::cmp::Ordering::Greater;
|
||||
}
|
||||
if self.y < other.y {
|
||||
return std::cmp::Ordering::Less;
|
||||
}
|
||||
if self.x > other.x {
|
||||
return std::cmp::Ordering::Greater;
|
||||
}
|
||||
if self.x < other.x {
|
||||
return std::cmp::Ordering::Less;
|
||||
}
|
||||
if self.y > other.y {
|
||||
return std::cmp::Ordering::Greater;
|
||||
}
|
||||
if self.y < other.y {
|
||||
return std::cmp::Ordering::Less;
|
||||
}
|
||||
|
||||
std::cmp::Ordering::Equal
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
307
src/space_colonization/spatial_index.rs
Normal file
307
src/space_colonization/spatial_index.rs
Normal 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]
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user