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

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

853
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

@ -3,7 +3,7 @@ use leptos::*;
#[component]
pub fn Subtitle(cx: Scope, class: &'static str, children: Children) -> impl IntoView {
let class = format!("text-3xl font-bold text-white {}", class);
view! { cx,
view! { cx,
<h3 class={class}>{children(cx)}</h3>
}
}
@ -15,7 +15,7 @@ pub fn SectionTitle(cx: Scope, dark: bool, children: Children) -> impl IntoView
textColorClass = "text-white";
}
let class = format!("text-5xl font-bold text-center {}", textColorClass);
view! { cx,
view! { cx,
<h2 class={class}>{children(cx)}</h2>
}
}
@ -27,9 +27,7 @@ pub fn SubsectionTitle(cx: Scope, dark: bool, children: Children) -> impl IntoVi
textColorClass = "text-white";
}
let class = format!("text-3xl font-bold {}", textColorClass);
view! { cx,
view! { cx,
<h5 class={class}>{children(cx)}</h5>
}
}

View File

@ -5,7 +5,7 @@ use leptos::*;
pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
view! { cx,
<div class="max-w-[1600px] mx-auto">
<div class="flex flex-col min-h-[700px] p-28 bg-cover bg-[url('/img/bg-page1.png')]">
<h1 class="text-orange-400 text-7xl font-extrabold mb-14">
"Micro centres de données"
@ -17,14 +17,14 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
</Subtitle>
<img src="/img/NationTech-logo.png" class="mx-auto opacity-50 w-1/4"/>
</div>
<div class="flex-col p-8">
<div class="w-full mx-auto mb-4 border-t-4 border-b-4 border-orange-400 p-4">
<SectionTitle dark=false>
"Énergie: le nerf de la guerre"
</SectionTitle>
</SectionTitle>
</div>
<div class="mr-72 ml-72">
<div class="mr-72 ml-72">
<p class="text-justify mb-10 mt-8">
"Tous les systèmes économiques sont basés sur deux variables fondamentales: le temps et lénergie. Depuis des millénaires lor est dispendieux. Parce quil requiert beaucoup de temps et dénergie à trouver. Depuis toujours leau a une valeur différente à différents endroits de la planète et moments de lannée, en fonction du temps et de lénergie requis pour la trouver. Dailleurs, lénergie est souvent représentée comme fonction du temps. Lunité dénergie choisie par le Système International, le Joule, sexprime en Kilogramme X Mètre2 X Seconde2. "
</p>
@ -32,14 +32,14 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
"Il convient donc de considérer lénergie comme fondement principal des différents systèmes économiques. Ce livre blanc vise à étudier les caractéristiques économiques selon différentes métriques naturelles, dont lénergie est la plus importante, des différentes technologies de déploiement de centres de données. Cette analyse vise plus particulièrement à comparer lefficacité énergétique, et donc économique, des déploiements de centres de données à très petite et très grande échelle."
</p>
<p class="text-right font-bold">
"Limpact global sera calculé en fonction des 5 métriques présentées ci-dessous."
"Limpact global sera calculé en fonction des 5 métriques présentées ci-dessous."
</p>
</div>
</div>
<div class="flex bg-zinc-200 h-[450px] max-[1150px]:h-fit max-[1150px]:flex-col flex-row justify-center items-center">
<div class="flex bg-zinc-200 h-[450px] max-[1150px]:h-fit max-[1150px]:flex-col flex-row justify-center items-center">
<div class="p-4 mt-8 mb-8 transform min-[1550px]:p-10 max-[1550px]:p-6 max-[1415px]:scale-75 max-[1340px]:p-0 max-[1150px]:scale-100 max-[1150px]:p-8">
<img src="/img/electricity-icon.png" class="mx-auto"/>
<img src="/img/electricity-icon.png" class="mx-auto"/>
<div class="flex justify-center items-center">
<h3 class="p-4 text-blue-950 text-5xl font-semibold">
"1"
@ -66,7 +66,7 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
<h3 class="p-4 text-blue-950 text-5xl font-semibold">
"3"
</h3>
<h4 class="font-Lato w-48 p-4 text-blue-950 text-xl font-medium my-auto">
<h4 class="font-Lato w-48 p-4 text-blue-950 text-xl font-medium my-auto">
"Impact environnemental"
</h4>
</div>
@ -77,7 +77,7 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
<h3 class="p-4 text-blue-950 text-5xl font-semibold">
"4"
</h3>
<h4 class="font-Lato w-48 p-4 text-blue-950 text-xl font-medium my-auto">
<h4 class="font-Lato w-48 p-4 text-blue-950 text-xl font-medium my-auto">
"Impact économique"
</h4>
</div>
@ -95,8 +95,8 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
</div>
</div>
<div class="justify-center items-center flex h-[250px] bg-cover bg-center bg-opacity-75 bg-[url('/img/server-wires-page3.png')]">
<div class="p-16 w-full">
<div class="justify-center items-center flex h-[250px] bg-cover bg-center bg-opacity-75 bg-[url('/img/server-wires-page3.png')]">
<div class="p-16 w-full">
<div class="w-full h-fit border-t-4 border-b-4 border-orange-400 p-4">
<SectionTitle dark=true>
"Qu'est-ce qu'un centre de données"
@ -106,27 +106,27 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
</div>
<div class="flex flex-col">
<div class="p-8 w-fit h-fit mr-72 ml-72">
<div class="w-fit pt-4 pb-4 border-r-8 border-indigo-500 pr-2">
<div class="w-fit pt-4 pb-4 border-r-8 border-indigo-500 pr-2">
<div class="flex-col">
<div class="flex justify-end pb-4">
<SubsectionTitle dark=true>
"Micro centres de données (MCD)"
</SubsectionTitle>
</div>
<p class="text-right mr-2 text-white">
<p class="text-right mr-2 text-white">
"Un MCD est une unité de serveurs de petite taille pouvant être installée dans nimporte quel immeuble, tant les maisons unifamiliales que les gratte-ciels ou les usines. Sa taille est adaptée aux besoins énergétiques de limmeuble qui labrite. Les rejets énergétiques, le plus souvent sous forme de chaleur, sont réutilisés au maximum dans limmeuble pour le chauffer. Ils peuvent également être utilisée pour chauffer leau, ou même regénérer de lélectricité dans certains cas. Ce type de déploiement réduit également les besoins de climatisation du centre de données puisque la chaleur est transférée à dautres usages. Lénergie totale consommée par le centre de données est donc déjà réduite."
</p>
</div>
</div>
</div>
<div class="p-8 w-fit h-fit pb-24 mr-72 ml-72">
<div class="w-fit pt-4 pb-4 border-l-8 border-indigo-500 pl-2">
<div class="w-fit pt-4 pb-4 border-l-8 border-indigo-500 pl-2">
<div class="flex-col">
<div class="flex justify-start pb-4">
<SubsectionTitle dark=true>
"Centres de données grande échelle (CDGE)"
</SubsectionTitle>
</div>
</div>
<div class="flex justify-start pb-4 text-white">
"Un CDGE est une unité constituée de milliers, voire de millions de serveurs centralisés sous un même toît. De tels serveurs sont généralement contenus dans dénormes bâtiments exclusivement dédiés à lhébergement de serveurs, ce qui requiert des systèmes de refroidissement énergivores."
</div>
@ -189,7 +189,7 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
<div class="flex flex-col justify-center">
</div>
<div class="flex-col p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-4">
<SectionTitle dark=false>
@ -199,7 +199,7 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
<div class="flex flex-row max-[1060px]:flex-col max-[1060px]:space-y-20 justify-center mt-16">
<div class="flex flex-col justify-center pl-40 pr-40 max-[1460px]:pl-16 max-[1460px]:pr-16">
<img src="/img/electricity-icon.png" class="transform mx-auto"/>
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
"Consommation d'énergie démesurée"
</h4>
</div>
@ -219,26 +219,26 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
<div class="flex flex-row max-[1060px]:flex-col min-[1060px]:pt-16 max-[1060px]:space-y-20 max-[1060px]:mt-20 justify-center">
<div class="flex flex-col justify-center pl-40 pr-40 max-[1460px]:pl-16 max-[1460px]:pr-16">
<img src="/img/coin-icon.png" class="transform mx-auto"/>
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
"Coûts exorbitants"
</h4>
</div>
<div class="flex flex-col justify-center pl-40 pr-40 max-[1460px]:pl-16 max-[1460px]:pr-16">
<img src="/img/group-icon.png" class="transform mx-auto"/>
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
"Impact social négatif"
</h4>
</div>
</div>
</div>
</div>
<div class="flex flex-col bg-zinc-200 justify-center p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<SectionTitle dark=false>
"Consommation d'énergie démesurée"
</SectionTitle>
</div>
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<p class="text-justify p-4 mb=32">
"On estime quen 2030, les technologies de linformation et de la communication, qui sont principalement basées sur lutilisation de serveurs informatiques, pourraient dans le pire des cas consommer 51% lélectricité mondiale et, dans le meilleur des cas, 8% de lélectricité mondiale (Andrae & Edler, 2015; Jones, 2018). On estime que 50% de lélectricité consommée par les CDGE sert au refroidissement des serveurs (The Impact of Data Centers on The Environment, 2022). Cette consommation dénergie est non seulement excessive, mais aussi hautement inefficiente. À titre illustratif, les résultats dune simulation réalisée dans le cadre dune étude chinoise réalisée en 2018 indiquaient que la charge cumulative annuelle de refroidissement dun CD était beaucoup plus élevée que la charge cumulative annuelle de chaleur (Yu et al., 2019, p. 154). Le potentiel de réutilisation de la chaleur est donc énorme, de même que le gaspillage énergétique actuel."
</p>
@ -251,7 +251,7 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
</div>
<div class="justify-center items-center flex h-[250px] bg-cover bg-center bg-opacity-90 bg-[url('/img/bg-water.jpg')]">
<div class="p-16 w-full">
<div class="p-16 w-full">
<div class="h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<SectionTitle dark=true>
"Consommation d'eau démesurée"
@ -269,10 +269,10 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
<div class="flex flex-col bg-zinc-200 justify-center p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<SectionTitle dark=false>
"Impact environnemental majeur"
</SectionTitle>
</SectionTitle>
</div>
<p class="text-justify mb-12 mt-8 ml-72 mr-72">
"Quant aux émissions de gaz à effet de serre (GES) des géants du web, le tableau suivant résume la situation en 2021 pour Amazon, Microsoft, Alphabet et Meta. À titre indicatif, lempreinte carbone per capita aux États-Unis est denviron 16 tonnes métriques déquivalent CO2 (tCO2e) et lempreinte per capita à léchelle planétaire est denviron 4 tCO2e (What Is Your Carbon Footprint? | Carbon Footprint Calculator, n.d.)"
@ -289,38 +289,38 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
"d'Amazon, Microsoft, Alphabet et Meta"
</h4>
<div class="bg-zinc-200 grid grid-cols-4 grid-rows-3 gap-y-4 ml-[450px] mr-[450px]">
<h4 class="flex fit-content items-center justify-center w-fit max-h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold">
<h4 class="flex fit-content items-center justify-center w-fit max-h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold">
"Catégories d'émissions"
</h4>
<div class="flex fit-content border-b-4 border-r-2 border-black pb-4">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-2xl font-bold">
<div class="flex fit-content border-b-4 border-r-2 border-black pb-4">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-2xl font-bold">
"Scope 1"
</h4>
</div>
<div class="flex fit-content border-b-4 border-r-2 border-black pb-4">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-2xl font-bold">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-2xl font-bold">
"Scope 2"
</h4>
</div>
<div class="flexfit-content border-b-4 border-black pb-4">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-2xl font-bold">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-2xl font-bold">
"Scope 3"
</h4>
</div>
<h4 class="flex fit-content items-center justify-center max-h-[68px] rounded-lg font-Lato bg-white mx-auto text-center w-fit p-4 text-blue-950 text-xl font-bold -translate-y-1">
<h4 class="flex fit-content items-center justify-center max-h-[68px] rounded-lg font-Lato bg-white mx-auto text-center w-fit p-4 text-blue-950 text-xl font-bold -translate-y-1">
"Quantité (tCO2e)"
</h4>
<div class="flex fit-content border-r-2 pt-4 border-black -translate-y-5">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold">
"9 837 398"
</h4>
</div>
<div class="flex fit-content border-r-2 pt-4 border-black -translate-y-5">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold">
"6 347 350"
</h4>
</div>
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold">
<h4 class="flex items-center justify-center w-[142px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold">
"74 566 000"
</h4>
<h4 class="flex items-center justify-center max-h-[68px] rounded-lg font-Lat bg-white mx-auto text-center p-4 text-blue-950 text-xl font-bold max-w-[175px] translate-y-1">
@ -330,74 +330,74 @@ pub fn WhyMicroDatacenters(cx: Scope) -> impl IntoView {
""
</div>
<div class="flex fit-content border-t-4 pt-4 border-black -translate-y-3">
<h4 class="flex items-center justify-center w-[300px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-2xl font-bold">
<h4 class="flex items-center justify-center w-[300px] h-[68px] rounded-lg font-Lato bg-white mx-auto text-center p-4 text-blue-950 text-2xl font-bold">
"90 750 748"
</h4>
</div>
<div class="flex border-t-4 pt-4 border-black -translate-y-3">
""
""
</div>
</div>
</div>
<div class="flex flex-col justify-center p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<SectionTitle dark=false>
"Coûts exorbitants"
</SectionTitle>
</div>
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<p class="text-justify p-4 mb=32">
"Le coût de construction, dexploitation et dentretien dun 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 dun coût de construction entre $140 M et $240 M."
</p>
</div>
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mb-12 mt-16 mr-72 ml-72 border-r-8 border-blue-400">
<p class="text-right p-4 mb=32">
"Or, la consommation électrique dun 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. "
</p>
</div>
</div>
<div class="flex flex-col justify-center p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<SectionTitle dark=true>
"Impact social négatif"
</SectionTitle>
</div>
<p class="text-justify mt-8 text-white ml-72 mr-72">
<p class="text-justify mt-8 text-white ml-72 mr-72">
"Les quatre problèmes ci-haut identifiés préoccupent de nombreuses personnes. Par exemple, dans les régions désertiques de lOuest américain où les big tech sinstallent pour labondance dénergie renouvelable, notamment lénergie solaire, plusieurs communautés sont préoccupées par les quantités astronomiques deau 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 deau 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: "
</p>
<p class="text-right italic mt-8 text-white ml-[450px] mr-[450px]">
<p class="text-right italic mt-8 text-white ml-[450px] mr-[450px]">
"When it comes to economic development, I dont 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 dont like to talk about the water."
</p>
<p class="text-right italic text-white ml-[450px] mr-[450px]">
"(Solon, 2021)"
</p>
<p class="text-justify mt-8 text-white ml-72 mr-72">
<p class="text-justify mt-8 text-white ml-72 mr-72">
"Similairement, Microsoft sest fait reprocher de manquer de transparence quant à son utilisation dans un de ces CD au Pays-Bas. Lentreprise avait indiqué quelle utiliserait de 12 M à 20 M de litres deau par an. Or, on découvrait quelle avait plutôt utilisé 84 M de litres deau en 2021, alors que des vagues de chaleur causaient dimportantes pénuries deau cette même année. Ce manque de transparence chronique est révélateur dun profond problème de responsabilité sociale de ces grandes industries."
</p>
<p class="text-justify mt-8 text-white ml-72 mr-72">
<p class="text-justify mt-8 text-white ml-72 mr-72">
"Le manque de transparence touche non seulement la gestion environnementale des projets, mais aussi le fonctionnement desdits CD comme lillustre le graphique ci-bas."
</p>
<h4 class="flex justify-center text-center mt-16 font-Lato text-white text-3xl font-extrabold ml-[450px] mr-[450px]">
"Compréhension du public quant à l'utilisation des CD et la gestion de leurs données"
</h4>
<div class="flex justify-center items-center p-8">
<img src="/img/graph-1.png"/>
<div class="flex justify-center items-center p-8">
<img src="/img/graph-1.png"/>
</div>
<p class="text-justify mt-2 text-neutral-400 text-xs ml-72 mr-72">
<p class="text-justify mt-2 text-neutral-400 text-xs ml-72 mr-72">
"Sources:"
</p>
<p class="text-justify mt-2 text-neutral-400 text-xs ml-72 mr-72">
<p class="text-justify mt-2 text-neutral-400 text-xs ml-72 mr-72">
"https://www.statista.com/statistics/617136/digital-population-worldwide/?fbclid=IwAR0bHpZ0u4JbW1hW2QI5WqJKmFhsmwJBwmviNVnesn-6rNhnQQCkbYrT1OU"
</p>
<p class="text-justify mt-2 text-neutral-400 text-xs ml-72 mr-72">
<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>
<div class="flex-col bg-zinc-200 p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<SectionTitle dark=false>
@ -407,7 +407,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<div class="flex flex-row max-[1060px]:flex-col max-[1060px]:space-y-20 justify-center mt-16">
<div class="flex flex-col justify-center pl-40 pr-40 max-[1460px]:pl-16 max-[1460px]:pr-16">
<img src="/img/electricity-icon.png" class="transform mx-auto"/>
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
"Consommation dénergie responsable et durable"
</h4>
</div>
@ -425,17 +425,17 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<div class="flex flex-row max-[1060px]:flex-col min-[1060px]:pt-16 max-[1060px]:space-y-20 max-[1060px]:mt-20 justify-center">
<div class="flex flex-col justify-center pl-40 pr-40 max-[1460px]:pl-16 max-[1460px]:pr-16">
<img src="/img/coin-icon.png" class="transform mx-auto"/>
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
"Faibles coûts"
</h4>
</div>
<div class="flex flex-col justify-center pl-40 pr-40 max-[1460px]:pl-16 max-[1460px]:pr-16">
<img src="/img/group-icon.png" class="transform mx-auto"/>
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
<h4 class="font-Lato mx-auto text-center w-64 p-4 text-blue-950 text-3xl font-bold">
"Impact social positif"
</h4>
</div>
</div>
</div>
</div>
<div class="justify-center items-center flex h-[250px] bg-cover bg-bottom bg-opacity-75 bg-[url('/img/solar-panel.jpg')]">
@ -449,25 +449,25 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</div>
</div>
</div>
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<p class="text-left p-4 mb=32">
"Les MCD, tels que ceux développés par NationTech, consomment une fraction de lénergie dun CDGE. La clé de cette augmentation de lefficacité é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 daération ou de refroidissement liquide qui la distribue afin de combler les besoins en chauffage deau et dair dun 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."
</p>
</div>
<div class="mt-8 mx-auto max-w-[425px]">
<SectionTitle dark=false>
"Rien ne se perd, Rien ne se crée, Tout se transforme"
"Rien ne se perd, Rien ne se crée, Tout se transforme"
</SectionTitle>
<p class="mt-2 text-right text-blue-950 font-sm">
"Antoine Lavoisier"
</p>
</div>
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mb-16 mt-16 mr-72 ml-72 border-r-8 border-blue-400">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mb-16 mt-16 mr-72 ml-72 border-r-8 border-blue-400">
<p class="text-right p-4 mb=32">
"Deux métriques sont ci-après utilisées afin de comparer lefficacité é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)."
</p>
</div>
<div class="flex-col bg-zinc-200 p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-4">
<SectionTitle dark=false>
@ -490,8 +490,8 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<h4 class="flex justify-center text-center mt-16 font-Lato text-blue-950 text-5xl font-extrabold">
"Power Usage Effectiveness (PUE)"
</h4>
<div class="flex justify-center items-center p-8">
<img src="/img/graph-2.png"/>
<div class="flex justify-center items-center p-8">
<img src="/img/graph-2.png"/>
</div>
<p class="justify-center text-right ml-48 mr-48">
"*Voir section sur le GPUE"
@ -521,24 +521,24 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<h4 class="flex justify-center text-center mt-16 font-Lato text-white text-5xl font-extrabold">
"Energy reuse factor (ERF)"
</h4>
<div class="flex justify-center items-center p-8">
<img src="/img/graph-3.png"/>
<div class="flex justify-center items-center p-8">
<img src="/img/graph-3.png"/>
</div>
</div>
</div>
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<p class="text-left p-4 mb=32">
"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 lensemble 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 nest réutilisée. En effet, on refroidit plutôt les installations avec des systèmes de climatisation énergivores ou bien avec de leau qui, une fois chaude, donc chargée énergétiquement, est rejetée dans la nature sans être réutilisée."
</p>
</div>
<div class="mb-8">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-r-8 border-blue-400">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-r-8 border-blue-400">
<p class="text-right p-4">
"Les géants de lindustrie ont quelques projets sur la table afin de réutiliser la chaleur de leurs serveurs. Cependant, pour le moment, cest une part infime de leurs CD qui recyclent la chaleur produite. Azure indique pourtant que le potentiel est énorme: selon les estimations de lentreprise, il serait possible datteindre un ERF de 0.69 en hiver et 0.84 en été."
</p>
</div>
</div>
<div class="flex-col p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-4">
<SectionTitle dark=true>
@ -559,12 +559,12 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
"Global Power Usage Effectiveness (GPUE)"
</h4>
<div class="flex justify-center items-center p-8">
<img src="/img/graph-4.png"/>
<img src="/img/graph-4.png"/>
</div>
</div>
</div>
<div class="mb-8">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<div class="flex flex-col justify-center pt-4 pb-4 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<p class="text-left p-4">
"Meta, Azure, Vantage, Equinix et AWS projettent toutes réutiliser lénergie de leurs centres de données afin datteindre la carboneutralité dici 2030. À lexception 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 na été trouvée quant à lintention de Google dimplémenter de tels projets. Voici un bref résumé de ces initiatives."
</p>
@ -575,7 +575,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<div class="flex flex-row">
<div class="flex w-1/2 justify-center items-center p-8">
<img src="/img/Facebook-logo.jpg" class="mx-auto mt-8 mb-8 rounded-lg w-1/2"/>
</div>
</div>
<div class="flex justify-center center w-1/2 bg-zinc-200">
<div class="my-auto">
<p class="mx-auto mr-32 ml-32 text-justify">
@ -587,7 +587,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<div class="flex flex-row">
<div class="flex w-1/2 justify-center items-center p-8">
<img src="/img/Microsoft-logo.jpg" class="h-[231.23px] mx-auto mt-8 mb-8 rounded-lg w-1/2"/>
</div>
</div>
<div class="flex justify-center center w-1/2 bg-zinc-200">
<div class="my-auto">
<p class="mx-auto mr-32 ml-32 text-justify">
@ -600,8 +600,8 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<div class="flex w-1/2 justify-center items-center p-8">
<div class="flex h-[231.23px] w-[368px] bg-white rounded-lg">
<img src="/img/Vantage-logo.png" class="mx-auto mt-8 mb-8 h-3/4 w-3/4"/>
</div>
</div>
</div>
</div>
<div class="flex justify-center center w-1/2 bg-zinc-200">
<div class="my-auto">
<p class="mx-auto mr-32 ml-32 text-justify">
@ -613,7 +613,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<div class="flex flex-row">
<div class="flex w-1/2 justify-center items-center p-8">
<img src="/img/Equinix-logo.jpg" class="h-[231.23px] mx-auto mt-8 mb-8 rounded-lg w-1/2"/>
</div>
</div>
<div class="flex justify-center center w-1/2 bg-zinc-200">
<div class="my-auto">
<p class="mx-auto mr-32 ml-32 text-justify">
@ -625,7 +625,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<div class="flex flex-row">
<div class="flex w-1/2 justify-center items-center p-8">
<img src="/img/Amazon-logo.jpg" class="h-[231.23px] mx-auto mt-8 mb-8 rounded-lg w-1/2"/>
</div>
</div>
<div class="flex justify-center center w-1/2 bg-zinc-200">
<div class="my-auto">
<p class="mx-auto mr-32 ml-32 text-justify">
@ -635,20 +635,20 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</div>
</div>
</div>
<div class="flex flex-col justify-center p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<SectionTitle dark=false>
"Consommation d'eau responsable et durable"
</SectionTitle>
</div>
<div class="flex flex-col justify-center pt-4 pb-4 mb-8 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<div class="flex flex-col justify-center pt-4 pb-4 mb-8 pl-2 mt-16 mr-72 ml-72 border-l-8 border-blue-400">
<p class="text-justify p-4 mb=32">
"Les MCD tels que développés par NationTech ne consomment pas deau. En effet, lentreprise offre deux options afin de distribuer la chaleur dans les immeubles quelle 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 limmeuble."
</p>
</div>
</div>
<div class="flex-col bg-zinc-200 p-8">
<div class="mx-auto h-fit w-full border-t-4 border-b-4 border-orange-400 p-4">
<SectionTitle dark=false>
@ -664,7 +664,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</p>
</div>
</div>
<div class="flex-col p-8">
<div class="flex justify-center flex-col ml-72 mr-72">
<p class="justify-center text-justify mt-8"> "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"
<p class="justify-center text-justify mt-8">
"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."
</p>
<h4 class="flex justify-center text-center mt-16 font-Lato text-5xl font-extrabold">
<h4 class="flex justify-center text-center mt-16 font-Lato text-5xl font-extrabold">
"Water Usage Effectiveness (WUE)"
</h4>
<div class="flex justify-center items-center p-8">
<img src="/img/graph-5.png" class="mt-8 mb-8 scale-125"/>
<img src="/img/graph-5.png" class="mt-8 mb-8 scale-125"/>
</div>
</div>
</div>
<div class="flex flex-col p-8 ">
<div class="pt-16 w-full">
<div class="pt-16 w-full">
<div class="h-fit w-full border-t-4 border-b-4 border-orange-400 p-8">
<SectionTitle dark=true>
"Impact environnemental nul"
@ -709,7 +709,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
"Température froide"
</p>
<div class="mb-4 flex flex-row">
<div class="flex items-center">
<div class="flex items-center">
<p class="mr-12 text-left font-extrabold text-4xl text-white">
"1"
</p>
@ -719,7 +719,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</p>
</div>
<div class="mb-4 flex flex-row">
<div class="flex items-center">
<div class="flex items-center">
<p class="mr-12 text-left font-extrabold text-4xl text-white">
"2"
</p>
@ -729,7 +729,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</p>
</div>
<div class="mb-4 flex flex-row">
<div class="flex items-center">
<div class="flex items-center">
<p class="mr-12 text-left font-extrabold text-4xl text-white">
"3"
</p>
@ -739,7 +739,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</p>
</div>
<div class="flex flex-row">
<div class="flex items-center">
<div class="flex items-center">
<p class="mr-12 text-left font-extrabold text-4xl text-white">
"4"
</p>
@ -747,9 +747,9 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
<p class="text-justify text-white">
"Cet air est utilisé pour chauffer la maison afin datteindre la température cible."
</p>
</div>
</div>
</div>
</div>
<div class="flex flex-row p-16">
<img src="/img/house-2.png" class="mr-4"/>
@ -758,7 +758,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
"Température chaude"
</p>
<div class="mb-4 flex flex-row">
<div class="flex items-center">
<div class="flex items-center">
<p class="mr-12 text-left font-extrabold text-4xl text-white">
"1"
</p>
@ -768,7 +768,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</p>
</div>
<div class="mb-4 flex flex-row">
<div class="flex items-center">
<div class="flex items-center">
<p class="mr-12 text-left font-extrabold text-4xl text-white">
"2"
</p>
@ -778,7 +778,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</p>
</div>
<div class="mb-4 flex flex-row">
<div class="flex items-center">
<div class="flex items-center">
<p class="mr-12 text-left font-extrabold text-4xl text-white">
"3"
</p>
@ -788,7 +788,7 @@ d=IwAR3wuxdHW8VE30FMaqZ6Iuj9Cs86bRSvmij1e_zDjM6Bwo4l71n5EKcLxqY"
</p>
</div>
<div class="flex flex-row">
<div class="flex items-center">
<div class="flex items-center">
<p class="mr-12 text-left font-extrabold text-4xl text-white">
"4"
</p>

View File

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

View File

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

View File

@ -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)));
}
}

View File

@ -1,14 +1,19 @@
use super::math::calculate_new_node_position;
use super::{Attractor, Node, NodeRef, Point};
use super::spatial_index::SpatialIndex;
use super::Attraction;
use super::{Attractor, Node, Point};
use log::info;
use rand::thread_rng;
use rand::Rng;
use std::collections::HashMap;
pub struct SpaceColonization {
pub struct SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
max_point: Point,
/// When a node grows within kill_distance of an attractor, the attractor is killed
kill_distance: i32,
kill_distance: f64,
/// Maximum distance between an attractor and a node for the node to
/// be affected by the attractor.
///
@ -40,34 +45,32 @@ pub struct SpaceColonization {
///
/// If density is 10, then there will be an average distance of 10 between attractors
density: i32,
new_nodes: Vec<(NodeRef, Node)>,
/// Flat list of all nodes in the tree
/// [node, child1, grand-child, child2, grand-child2]
nodes: Vec<Node>,
pub attractors: Vec<Attractor>,
pub attractors: SpatialIndex<Attractor>,
render_fn: F,
}
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
let mut nodes_vec = Vec::new();
nodes_vec.push(Node {
position: Point { x: 100, y: 100 },
children: Vec::new(),
});
let attractors = Vec::new();
impl<F> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
pub fn new(width: i32, height: i32, render_fn: F) -> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
let attraction_distance = 100;
let attractors = SpatialIndex::new(Point::new((width, height)), attraction_distance);
let mut sc = SpaceColonization {
max_point: Point {
x: width,
y: height,
},
kill_distance: 5,
attraction_distance: 100,
kill_distance: 5.0,
attraction_distance,
segment_length: 5,
density: 30,
nodes: nodes_vec,
attractors,
new_nodes: Vec::new(),
render_fn,
};
sc.place_attractors();
@ -79,30 +82,32 @@ impl SpaceColonization {
pub fn new_for_tests(
width: i32,
height: i32,
nodes: Vec<Node>,
attractors: Vec<Attractor>,
) -> SpaceColonization {
attractors: SpatialIndex<Attractor>,
render_fn: F,
) -> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
SpaceColonization {
max_point: Point {
x: width,
y: height,
},
kill_distance: 5,
kill_distance: 5.0,
attraction_distance: 12,
segment_length: 3,
density: 3,
nodes,
attractors,
new_nodes: Vec::new(),
render_fn,
}
}
pub fn render_nodes<F>(&self, render_id: f64, render_fn: F)
pub fn render_all_nodes<G>(&self, nodes: &Vec<Node>, render_id: f64, render_fn: G)
where
F: Copy + Fn(&Node, &Node),
G: Copy + Fn(&Node, &Node),
{
info!("Rendering {} nodes", self.nodes.len());
for n in self.nodes.iter() {
info!("Rendering {} nodes", nodes.len());
for n in nodes.iter() {
n.render(render_id, render_fn);
}
}
@ -112,10 +117,8 @@ impl SpaceColonization {
let mut y_pos = 0;
while x_pos < self.max_point.x {
while y_pos < self.max_point.y {
self.attractors.push(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()),
dead: false,
});
let point = self.get_random_point(x_pos.into(), y_pos.into());
self.attractors.add(&point, Attractor::new(point));
y_pos += self.density;
}
x_pos += self.density;
@ -147,75 +150,127 @@ impl SpaceColonization {
}
}
pub fn grow(&self) {
// TODO
// Find a clean API that will be stable across refactoring
// Write the test against this api including performance
// Find a way to make a compile-time safe datastructure that will ensure that
// - I can store my attractors and their state (remove them or update them when dead)
// - I can store my nodes and their state
// - I can efficiently render my nodes on a canvas
// - I use as little memory as possible
// - I can update my nodes
self.grow_nodes();
println!("new nodes for iteration {:?}", self.new_nodes);
let mut nodes_mut = self.nodes;
for new_pair in self.new_nodes.iter() {
new_pair.0.children.push(new_pair.1);
nodes_mut.push(new_pair.1);
}
self.new_nodes.clear();
pub fn grow<'b>(&mut self, mut nodes: &'b mut Vec<Node>) {
self.grow_nodes(&mut nodes)
}
pub fn grow_nodes(&self) {
pub fn grow_nodes(&mut self, nodes: &mut Vec<Node>) {
// iterate through attractors
// find closest node within attraction range
// build a map of nodes to affecting attractors
// attractors within the attraction range that this node is the closest to
//
// calculate new node position
let attractors = self.attractors;
let mut growing_paths: HashMap<<NodeRef>, Vec<AttractorRef>> = HashMap::new();
for a in attractors.iter() {
if a.dead {
continue;
}
let mut closest_node: Option<Node> = None;
let mut closest_node_distance = f64::MAX;
for n in self.nodes.iter() {
let distance = n.position.distance(&a.position);
if distance <= self.attraction_distance as f64 {
// TODO make sure it is closest node amongs all nodes
if distance < closest_node_distance {
closest_node = Some(n);
closest_node_distance = distance;
if distance < self.kill_distance as f64 {
a.dead.replace(true);
// ------------ START OF BLOCK ----------
// DO NOT MODIFY THE NODES VEC AFTER THIS
// We are taking raw pointers to Node to be dereferenced later, if the Vec of nodes is
// modified it will cause wrong behavior or segmentation faults and crash
let mut attractor_to_closest_node: HashMap<*mut Attractor, Attraction> = HashMap::new();
for n in nodes.iter_mut() {
self.build_attractor_to_closest_node(&mut attractor_to_closest_node, n);
}
let mut node_to_attractors: HashMap<*mut Node, Vec<*mut Attractor>> = HashMap::new();
attractor_to_closest_node
.drain()
.for_each(|(attractor, attraction)| {
let mut node_attractors = match node_to_attractors.remove(&attraction.node) {
Some(node_a) => node_a,
None => Vec::new(),
};
node_attractors.push(attractor);
node_to_attractors.insert(attraction.node, node_attractors);
});
let mut dead_attractors: Vec<*mut Attractor> = Vec::new();
node_to_attractors.iter().for_each(|(node, attractor)| {
// Unsafe is used here for two main reasons :
//
// PERFORMANCE : Using unsafe here allows to store multiple mutable references to a
// Node and save a few bytes of memory and cpu cycles to store a struct that holds
// a fake reference to the Node, such as the path in the tree, and then resolve it
// handling the Option<> every step of the way.
//
// Using a raw pointer we can Oh I actually just realised having a raw pointer deep
// in a tree of Vec that are getting pushed into might very well cause the pointer
// to become invalid when its parent gets pushed into and moved to another memory
// space
//
// Using raw fixed length arrays would solve that but its a fine line between too
// large memory usage and enough children nodes
//
// LEARNING : using unsafe here sounded like not the worst idea and was a nice
// opportunity to learn, which did happen. I am closing in now and I can say that I
// have learned quite a lot about how rust programs work and should be designed.
unsafe {
let new_point =
calculate_new_node_position(&(**node), attractor, self.segment_length);
if let Some(new_point) = new_point {
let new_node = Node::new(new_point);
attractor.iter().for_each(|a| {
if (**a).position.distance(&new_node.position) <= self.kill_distance {
dead_attractors.push(*a);
(**a).dead = true;
}
}
});
(self.render_fn)(&**node, &new_node);
(**node).children.push(new_node);
}
}
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);
// 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());
}
}

View File

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

View File

@ -0,0 +1,307 @@
use super::Point;
#[derive(Debug)]
pub struct SpatialIndex<T> {
elements: Vec<Vec<T>>,
/// The number of cells between a cell and the cell right below it.
///
/// For a table of 100x100 with cell_size = 10, number_cells_x is 11 (100/10+1)
number_cells_x: i32,
cell_size: i32,
size: u32,
max_point: Point,
}
impl<T> SpatialIndex<T>
where
T: std::fmt::Debug,
{
pub fn new(max_point: Point, cell_size: i32) -> SpatialIndex<T> {
// Here we add 1 to number cells x and y to handle the right and bottom edges where
// x = max_point.x or y = max_point.y
// For example, with max_point of (100,100)
// the point 100,100 is valid but 100/10 = 10, which is out of bounds of a zero based
// array of length 10. So we add 1 to the length and 10 is a valid index in a zero based
// array of length 11
let number_cells_x = (max_point.x / cell_size) + 1;
let number_cells = number_cells_x * ((max_point.y / cell_size) + 1);
let mut elements = Vec::with_capacity(number_cells as usize);
for _ in 0..number_cells {
elements.push(Vec::new());
}
SpatialIndex {
elements,
cell_size,
number_cells_x,
size: 0,
max_point,
}
}
/// Adds an element in the cell of point
///
/// panics if the point is outside of the grid
pub fn add(&mut self, point: &Point, element: T) {
let element_index = self.get_index_from_position(point);
self.elements
.get_mut(element_index as usize)
.unwrap()
.push(element);
self.size += 1;
}
pub fn size(&self) -> u32 {
self.size
}
/// Fetches elements in the cell corresponding to point and all the adjacent cells
/// filtering returned elements with filter
///
/// May panic or return wrong values if the point is outside this SpatialIndex max point
pub fn get_surrounding_elements_with_filter<'a, F>(
self: &'a mut Self,
point: &Point,
mut filter: F,
) -> Vec<*mut T>
where
F: FnMut(&T) -> bool,
{
let surrounding_indices: Vec<usize> = self.get_surrounding_indices(point);
let mut surrounding_elements: Vec<*mut T> = Vec::new();
for i in surrounding_indices.iter() {
println!("elements len {}", self.elements.len());
let elements_for_index = self
.elements
.get_mut(*i as usize)
.expect(
format!(
"point {:?} is in range for spatial_index with max_point {:?}, currently getting index {}, among surrounding indices {:?}",
point, self.max_point, i, surrounding_indices
)
.as_str(),
);
let elements_for_index = elements_for_index
.iter_mut()
.filter(|el| filter(el))
.map(|el| el as *mut T);
surrounding_elements.extend(elements_for_index);
}
surrounding_elements
}
fn get_index_from_position(&self, point: &Point) -> usize {
((point.x / self.cell_size) + ((point.y / self.cell_size) * self.number_cells_x)) as usize
}
fn get_surrounding_indices(&self, point: &Point) -> Vec<usize> {
let element_index = self.get_index_from_position(point);
let mut indices = Vec::from([element_index]);
let row_offset = self.number_cells_x as usize;
let last_cell_x_start = self.max_point.x - (self.max_point.x % self.cell_size);
let last_row_y_min = self.max_point.y - (self.max_point.y % self.cell_size);
// top row
if point.y >= self.cell_size {
// top left
if point.x >= self.cell_size {
indices.push(element_index - row_offset - 1);
}
// top middle
indices.push(element_index - row_offset);
// top right
if point.x < last_cell_x_start {
indices.push(element_index - row_offset + 1);
}
}
// middle left
if point.x >= self.cell_size {
indices.push(element_index - 1);
}
// middle middle can be skipped, already added
// middle right
if point.x < last_cell_x_start {
indices.push(element_index + 1);
}
if point.y < last_row_y_min {
// bottom left
if point.x >= self.cell_size {
indices.push(element_index + row_offset - 1);
}
// bottom middle
indices.push(element_index + row_offset);
// bottom right
if point.x < last_cell_x_start {
indices.push(element_index + row_offset + 1);
}
}
indices
}
}
#[cfg(test)]
mod test {
use super::*;
fn assert_vec_values(actual: Vec<*mut usize>, expected: Vec<usize>) {
let resolved_actual: Vec<usize>;
unsafe {
resolved_actual = actual.iter().map(|n| **n).collect();
}
assert_eq!(resolved_actual, expected);
}
#[test]
fn when_no_element_surrounding_nodes_returns_empty_vec() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
assert_eq!(
index.get_surrounding_elements_with_filter(&Point::new((0, 0)), |_| true),
Vec::<*mut usize>::new()
);
}
#[test]
fn added_point_is_surrounding_itself() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((50, 50)), 132);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 50)), |_| true),
vec![132],
);
}
#[test]
fn multiple_points_in_every_surrounding_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((40, 40)), 4040);
index.add(&Point::new((45, 45)), 4040);
index.add(&Point::new((50, 40)), 5040);
index.add(&Point::new((60, 40)), 6040);
index.add(&Point::new((40, 50)), 4050);
index.add(&Point::new((50, 50)), 5050);
index.add(&Point::new((60, 50)), 6050);
index.add(&Point::new((50, 50)), 5050);
index.add(&Point::new((51, 51)), 5151);
index.add(&Point::new((60, 60)), 6060);
index.add(&Point::new((40, 60)), 4060);
index.add(&Point::new((50, 60)), 5060);
index.add(&Point::new((60, 60)), 6060);
assert_eq!(
index
.get_surrounding_elements_with_filter(&Point::new((50, 50)), |_| true)
.sort(),
vec![
&4040, &4545, &5040, &6040, &4050, &5050, &6050, &5050, &5151, &6060, &4060, &5060,
&6060,
]
.sort()
);
}
#[test]
fn point_on_top_edge_is_close_to_first_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((50, 0)), 132);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 9)), |_| true),
vec![132],
);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 0)), |_| true),
vec![132],
);
}
#[test]
fn point_on_bottom_edge_is_close_to_bottom_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((50, 100)), 132);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 95)), |_| true),
vec![132],
);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((50, 100)), |_| true),
vec![132],
);
}
#[test]
fn point_on_right_edge_is_close_to_first_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((100, 50)), 132);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((95, 50)), |_| true),
vec![132],
);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((100, 50)), |_| true),
vec![132],
);
}
#[test]
fn point_on_left_edge_is_close_to_first_cell() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((0, 50)), 132);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((9, 50)), |_| true),
vec![132],
);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((0, 50)), |_| true),
vec![132],
);
}
#[test]
fn when_elements_too_far_surrounding_is_empty() {
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((100, 100)), 10);
index.add(&Point::new((0, 0)), 132);
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((99, 99)), |_| true),
Vec::<usize>::new(),
);
}
#[test]
fn works_when_max_point_is_not_multiple_of_cell_size() {
let cell_size = 100;
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((1920, 580)), cell_size);
for x_cell in 0..19 as usize {
for y_cell in 0..5 as usize {
index.add(
&Point::new((x_cell as i32 * cell_size, y_cell as i32 * cell_size)),
x_cell * 100 + y_cell,
);
}
}
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((1833, 502)), |_| true),
vec![1905, 1904, 1805, 1804]
);
}
#[test]
fn works_when_max_point_is_not_multiple_of_cell_size() {
let cell_size = 100;
let mut index: SpatialIndex<usize> = SpatialIndex::new(Point::new((1920, 580)), cell_size);
for x_cell in 0..19 as usize {
for y_cell in 0..5 as usize {
index.add(
&Point::new((x_cell as i32 * cell_size, y_cell as i32 * cell_size)),
x_cell * 100 + y_cell,
);
}
}
assert_vec_values(
index.get_surrounding_elements_with_filter(&Point::new((1833, 502)), |_| true),
vec![1905, 1904, 1805, 1804]
);
}
}