+
"Le coût de construction, d’exploitation et d’entretien d’un CDGE est extrêmement élevé. En effet, le coût de construction oscille entre $7 millions et $12 millions par mégawatt (MW) (Zhang, 2022a). Sachant que les centres de données de Google Cloud Platform (GCP) consomment en moyenne 20 MW, on parle d’un coût de construction entre $140 M et $240 M."
-
+
"Or, la consommation électrique d’un CD en MW ne représente pas nécessairement sa capacité de stockage ou de traitement des données. En effet, leur consommation électrique est non seulement liée au fonctionnement des équipements informatiques, mais aussi en grande partie aux besoins de refroidissement et de distribution d'énergie. Cette consommation d’électricité augmente donc rapidement avec la taille des centres de données. "
"Impact social négatif"
-
+
"Les quatre problèmes ci-haut identifiés préoccupent de nombreuses personnes. Par exemple, dans les régions désertiques de l’Ouest américain où les big tech s’installent pour l’abondance d’énergie renouvelable, notamment l’énergie solaire, plusieurs communautés sont préoccupées par les quantités astronomiques d’eau consommées par les CDGE (Solon, 2021). En Arizona, où Apple a élu domicile pour un de ses CD, on indique que le niveau du principal réservoir d’eau de la région a atteint de bas niveaux historiques (James, 2021). La mairesse de Mesa, ville où se trouve le CD en question, se confiait à BBC News en 2021: "
-
+
"When it comes to economic development, I don’t think we are fully transparent about the water concerns [...] We want to keep the image that we are a great place to invest and start a business. But we don’t like to talk about the water."
"(Solon, 2021)"
-
+
"Similairement, Microsoft s’est fait reprocher de manquer de transparence quant à son utilisation dans un de ces CD au Pays-Bas. L’entreprise avait indiqué qu’elle utiliserait de 12 M à 20 M de litres d’eau par an. Or, on découvrait qu’elle avait plutôt utilisé 84 M de litres d’eau en 2021, alors que des vagues de chaleur causaient d’importantes pénuries d’eau cette même année. Ce manque de transparence chronique est révélateur d’un profond problème de responsabilité sociale de ces grandes industries."
-
+
"Le manque de transparence touche non seulement la gestion environnementale des projets, mais aussi le fonctionnement desdits CD comme l’illustre le graphique ci-bas."
-
-

+
+
-
+
"Sources:"
-
+
"https://www.statista.com/statistics/617136/digital-population-worldwide/?fbclid=IwAR0bHpZ0u4JbW1hW2QI5WqJKmFhsmwJBwmviNVnesn-6rNhnQQCkbYrT1OU"
-
+
"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"
-
+
@@ -407,7 +407,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"

-
+
"Consommation d’énergie responsable et durable"
@@ -425,17 +425,17 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"

-
+
"Faibles coûts"

-
+
"Impact social positif"
-
+
@@ -449,25 +449,25 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
+
"Les MCD, tels que ceux développés par NationTech, consomment une fraction de l’énergie d’un CDGE. La clé de cette augmentation de l’efficacité énergétique est la réutilisation de la chaleur. En effet, les CDGE refroidissent l’équipement informatique grâce à des systèmes de climatisation énergivores tandis que les MCD extraient la chaleur via un système d’aération ou de refroidissement liquide qui la distribue afin de combler les besoins en chauffage d’eau et d’air d’un immeuble donné. Ainsi, l’électricité normalement utilisée afin de faire chauffer une résistance dans un radiateur servira plutôt à faire chauffer des serveurs. Suivant un principe physique de base, les rejets thermiques étant pratiquement identiques."
- "Rien ne se perd, Rien ne se crée, Tout se transforme"
+ "Rien ne se perd, Rien ne se crée, Tout se transforme"
"Antoine Lavoisier"
-
+
"Deux métriques sont ci-après utilisées afin de comparer l’efficacité énergétique des MCD par rapport à celle des CDGE: le power usage effectiveness (PUE) ainsi que le energy reuse factor (ERF). NationTech a créé une troisième métrique permettant de fusionner les deux première: le global power usage effectiveness (GPUE)."
-
+
@@ -490,8 +490,8 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
-

+
+
"*Voir section sur le GPUE"
@@ -521,24 +521,24 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
-

+
+
-
+
"Les MCD visent à réutiliser la majorité de l’énergie utilisée afin de faire fonctionner le CD. Les rejets thermiques de l’équipement informatiques, soit l’ensemble de l’énergie entrant dans le CD moins l’électricité utilisée pour les ventilateurs et le courant électrique passant dans la fibre optique, est réutilisée afin de chauffer un immeuble. Dans le cas des CDGE, aucune énergie n’est réutilisée. En effet, on refroidit plutôt les installations avec des systèmes de climatisation énergivores ou bien avec de l’eau qui, une fois chaude, donc chargée énergétiquement, est rejetée dans la nature sans être réutilisée."
-
+
"Les géants de l’industrie ont quelques projets sur la table afin de réutiliser la chaleur de leurs serveurs. Cependant, pour le moment, c’est une part infime de leurs CD qui recyclent la chaleur produite. Azure indique pourtant que le potentiel est énorme: selon les estimations de l’entreprise, il serait possible d’atteindre un ERF de 0.69 en hiver et 0.84 en été."
-
+
@@ -559,12 +559,12 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
"Global Power Usage Effectiveness (GPUE)"
-

+
-
+
"Meta, Azure, Vantage, Equinix et AWS projettent toutes réutiliser l’énergie de leurs centres de données afin d’atteindre la carboneutralité d’ici 2030. À l’exception de Vantage, elles ont toutes implémenter des projets de réutilisation de la chaleur produite par l’équipement informatique de leurs centres de données. Aucune information n’a été trouvée quant à l’intention de Google d’implémenter de tels projets. Voici un bref résumé de ces initiatives."
@@ -575,7 +575,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"

-
+
@@ -587,7 +587,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"

-
+
@@ -600,8 +600,8 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"

-
-
+
+
@@ -613,7 +613,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"

-
+
@@ -625,7 +625,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"

-
+
@@ -635,20 +635,20 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
+
-
+
"Consommation d'eau responsable et durable"
-
+
"Les MCD tels que développés par NationTech ne consomment pas d’eau. En effet, l’entreprise offre deux options afin de distribuer la chaleur dans les immeubles qu’elle chauffe: soit un système de ventilation conventionnel distribuant la chaleur des serveurs grâce à des ventilateurs, soit un système de refroidissement liquide qui absorbe la chaleur produite pour la redistribuer dans l’immeuble."
-
+
@@ -664,7 +664,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
+
"La figure suivante présente le WUE moyen des entreprise Meta, Google, Microsoft, Vantage, Equinix et Amazon pour leur CDGE ainsi que le WUE de NationTech dans ses MCD."
@@ -677,17 +677,17 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
"La figure suivante présente le WUE moyen des entreprise Meta, Google, Microsoft, Vantage, Equinix et Amazon pour leur CDGE ainsi que le WUE de NationTech dans ses MCD."
-
-
+
"Impact environnemental nul"
@@ -709,7 +709,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
"Température froide"
-
+
@@ -719,7 +719,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
+
@@ -729,7 +729,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
+
@@ -739,7 +739,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
+
@@ -747,9 +747,9 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
"Cet air est utilisé pour chauffer la maison afin d’atteindre la température cible."
-
+
-
+

@@ -758,7 +758,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
"Température chaude"
-
+
@@ -768,7 +768,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
+
@@ -778,7 +778,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
+
@@ -788,7 +788,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
-
+
diff --git a/src/space_colonization/math.rs b/src/space_colonization/math.rs
index 756dfd3..98f34c2 100644
--- a/src/space_colonization/math.rs
+++ b/src/space_colonization/math.rs
@@ -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
{
+ 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 {
+ 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()
}
}
}
diff --git a/src/space_colonization/mod.rs b/src/space_colonization/mod.rs
index 1447000..4c6e079 100644
--- a/src/space_colonization/mod.rs
+++ b/src/space_colonization/mod.rs
@@ -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,
}
-#[derive(Debug)]
-pub struct NodeRef {
- path: Vec
+pub struct Attraction {
+ node: *mut Node,
+ distance: f64,
}
-
-#[derive(Debug)]
-pub struct AttractorRef {
- path: Vec
+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(&self, render_id: f64, render_fn: F)
+ pub fn render(&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(),
}
}
diff --git a/src/space_colonization/point.rs b/src/space_colonization/point.rs
index 94e39ea..e667fdf 100644
--- a/src/space_colonization/point.rs
+++ b/src/space_colonization/point.rs
@@ -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)));
}
}
-
diff --git a/src/space_colonization/space_colonization.rs b/src/space_colonization/space_colonization.rs
index bb0babc..43598f1 100644
--- a/src/space_colonization/space_colonization.rs
+++ b/src/space_colonization/space_colonization.rs
@@ -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
+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,
- pub attractors: Vec,
+ pub attractors: SpatialIndex,
+ 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 SpaceColonization
+where
+ F: Fn(&Node, &Node),
+{
+ pub fn new(width: i32, height: i32, render_fn: F) -> SpaceColonization
+ 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,
- attractors: Vec,
- ) -> SpaceColonization {
+ attractors: SpatialIndex,
+ render_fn: F,
+ ) -> SpaceColonization
+ 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(&self, render_id: f64, render_fn: F)
+ pub fn render_all_nodes(&self, nodes: &Vec, 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) {
+ self.grow_nodes(&mut nodes)
}
- pub fn grow_nodes(&self) {
+ pub fn grow_nodes(&mut self, nodes: &mut Vec) {
// 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<, Vec> = HashMap::new();
- for a in attractors.iter() {
- if a.dead {
- continue;
- }
- let mut closest_node: Option = 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,
+ nodes: &'a Vec,
+ 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::>().len(), 1);
-
- // TODO assert nodes 3,0 and 6,0
- assert_eq!(sc.nodes.len(), 3);
+ sc.grow(&mut nodes);
+ println!("after grow 2");
+ dbg!(&nodes);
+
+ 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());
}
}
diff --git a/src/space_colonization/space_colonization.rs.bak b/src/space_colonization/space_colonization.rs.bak
deleted file mode 100644
index 8692413..0000000
--- a/src/space_colonization/space_colonization.rs.bak
+++ /dev/null
@@ -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>,
- pub attractors: Vec>,
-}
-
-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(&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) {
- 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) -> Vec<&RwLock> {
- 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,
- affecting_attractors: &Vec<&RwLock>,
- ) -> 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;
- }
-}
diff --git a/src/space_colonization/spatial_index.rs b/src/space_colonization/spatial_index.rs
new file mode 100644
index 0000000..c4246fc
--- /dev/null
+++ b/src/space_colonization/spatial_index.rs
@@ -0,0 +1,307 @@
+use super::Point;
+
+#[derive(Debug)]
+pub struct SpatialIndex {
+ elements: Vec>,
+ /// 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 SpatialIndex
+where
+ T: std::fmt::Debug,
+{
+ pub fn new(max_point: Point, cell_size: i32) -> SpatialIndex {
+ // 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 = 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 {
+ 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) {
+ let resolved_actual: Vec;
+ 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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::::new(),
+ );
+ }
+
+ #[test]
+ fn works_when_max_point_is_not_multiple_of_cell_size() {
+ let cell_size = 100;
+ let mut index: SpatialIndex = 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 = 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]
+ );
+ }
+}