space colonization, fooling around with rust

This commit is contained in:
Jean-Gabriel Gill-Couture 2023-07-23 23:45:00 -04:00
parent 6f2505736c
commit 4ef577e76b
5 changed files with 267 additions and 115 deletions

View File

@ -18,7 +18,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
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());
let mut 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 = {} {}",
@ -38,8 +38,8 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
context.set_fill_style(&JsValue::from("yellow"));
log!("About to render nodes");
let start_time = window().unwrap().performance().unwrap().now();
for n in sc.root_nodes.iter() {
context.fill_rect(n.read().unwrap().position.x.into(), n.read().unwrap().position.y.into(), 5.0, 5.0);
for n in sc.root_nodes.borrow().iter() {
context.fill_rect(n.position.x.into(), n.position.y.into(), 5.0, 5.0);
}
context.begin_path();
@ -52,14 +52,14 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
context.stroke();
context.set_fill_style(&JsValue::from("magenta"));
for a in sc.attractors.iter() {
context.fill_rect(a.read().unwrap().position.x.into(), a.read().unwrap().position.y.into(), 5.0, 5.0);
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.root_nodes.len(),
sc.attractors.len(),
sc.root_nodes.borrow().len(),
sc.attractors.borrow().len(),
end_time - start_time
);

View File

@ -1,4 +1,4 @@
use std::sync::RwLock;
use std::cell::RefCell;
use wasm_bindgen::prelude::*;
@ -13,25 +13,33 @@ extern "C" {
fn performance() -> web_sys::Performance;
}
#[derive(Debug)]
pub struct Attractor {
pub position: Point,
pub dead: bool,
}
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub struct Node {
pub position: Point,
pub children: Vec<RwLock<Node>>,
pub children: RefCell<Vec<Node>>,
}
impl std::hash::Hash for Node {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.position.hash(state);
}
}
impl Node {
pub fn render<F>(&self, render_id: f64, render_fn: F) where F: Copy + Fn(&Node, &Node) {
for child in self.children.iter() {
render_fn(self, &child.read().unwrap());
let children = self.children.borrow();
for child in children.iter() {
render_fn(self, &child);
}
for child in self.children.iter() {
child.read().unwrap().render(render_id, render_fn);
for child in children.iter() {
child.render(render_id, render_fn);
}
}
}

View File

@ -1,4 +1,4 @@
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Point {
pub x: i32,
pub y: i32,

View File

@ -1,4 +1,6 @@
use std::sync::RwLock;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use super::{Attractor, Node, Point};
use rand::thread_rng;
@ -20,18 +22,18 @@ pub struct SpaceColonization {
///
/// 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>>,
pub root_nodes: Rc<RefCell<Vec<Node>>>,
pub attractors: Rc<RefCell<Vec<Attractor>>>,
}
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
let mut root_nodes = Vec::new();
root_nodes.push(RwLock::new(Node {
let root_nodes = Rc::new(RefCell::new(Vec::new()));
root_nodes.borrow_mut().push(Node {
position: Point { x: 100, y: 100 },
children: Vec::new(),
}));
let attractors = Vec::new();
children: RefCell::new(Vec::new()),
});
let attractors = Rc::new(RefCell::new(Vec::new()));
let mut sc = SpaceColonization {
max_point: Point {
@ -55,8 +57,8 @@ impl SpaceColonization {
where
F: Copy + Fn(&Node, &Node),
{
for n in self.root_nodes.iter() {
n.read().unwrap().render(render_id, render_fn);
for n in self.root_nodes.borrow().iter() {
n.render(render_id, render_fn);
}
}
@ -67,10 +69,10 @@ impl SpaceColonization {
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 {
self.attractors.borrow_mut().push(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()),
dead: false,
}));
});
y_pos += self.density;
}
x_pos += self.density;
@ -105,97 +107,38 @@ impl SpaceColonization {
}
}
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
}
pub fn grow(&mut self) {
// 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 root_nodes = self.root_nodes.borrow();
let attractors = self.attractors.borrow();
let mut growing_paths : HashMap<&Node, Vec<&Attractor>> = HashMap::new();
for a in attractors.iter() {
let mut closest_node: Option<&Node> = None;
let mut closest_node_distance = f64::MAX;
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;
for n in root_nodes.iter() { // TODO iterate on children nodes
let distance = n.position.distance(&a.position);
if distance <= self.attraction_distance as f64 {
console::log_1(&format!("found node within attraction distance {:?}", a).into());
if distance < closest_node_distance {
closest_node = Some(n);
closest_node_distance = distance;
}
}
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 let Some(node) = closest_node {
let attractors = match growing_paths.get_mut(node) {
Some(a) => a.take(),
None => Vec::new(),
};
if n_read.position.distance(&a.position) < node_distance {
return false;
}
// todo iterate the entire tree
todo!();
}
return true;
console::log_1(&format!("found {} pairs ", growing_paths.len()).into());
}
}

View File

@ -0,0 +1,201 @@
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;
}
}