It works! Or does it? Have to inspect carefully
This commit is contained in:
parent
93781cae4a
commit
64c3987cae
@ -63,7 +63,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
|
|||||||
end_time - start_time
|
end_time - start_time
|
||||||
);
|
);
|
||||||
|
|
||||||
for _i in 1..15 {
|
for _i in 1..5 {
|
||||||
sc.grow();
|
sc.grow();
|
||||||
let render_id = window().unwrap().performance().unwrap().now();
|
let render_id = window().unwrap().performance().unwrap().now();
|
||||||
context.begin_path();
|
context.begin_path();
|
||||||
|
|||||||
0
src/space_colonization/log.rs
Normal file
0
src/space_colonization/log.rs
Normal file
70
src/space_colonization/math.rs
Normal file
70
src/space_colonization/math.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use super::{Attractor, Node, Point};
|
||||||
|
|
||||||
|
pub fn calculate_new_node_position(
|
||||||
|
growth_cell: &(Rc<Node>, Vec<&Attractor>),
|
||||||
|
segment_length: u16,
|
||||||
|
) -> Point {
|
||||||
|
let node = &growth_cell.0;
|
||||||
|
let attractors = &growth_cell.1;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
node.position.movement(point, segment_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
const SEGMENT_LENGTH: u16 = 5;
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
assert_eq!(point, Point::new((0, 5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_node_ignores_dead_attractor() {}
|
||||||
|
|
||||||
|
struct GrowthCell {
|
||||||
|
node: Rc<Node>,
|
||||||
|
attractors: Vec<Attractor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GrowthCell {
|
||||||
|
pub fn from_positions(positions: Vec<(i32, i32)>) -> Self {
|
||||||
|
assert!(positions.len() >= 2);
|
||||||
|
let node = Rc::new(Node {
|
||||||
|
position: Point::new(positions[0]),
|
||||||
|
children: Vec::new().into(),
|
||||||
|
});
|
||||||
|
let mut attractors = Vec::new();
|
||||||
|
for p in positions.iter().skip(1) {
|
||||||
|
attractors.push(Attractor {
|
||||||
|
position: Point::new(*p),
|
||||||
|
dead: RefCell::new(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Self { node, attractors }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_refs(&self) -> (Rc<Node>, Vec<&Attractor>) {
|
||||||
|
(self.node.clone(), self.attractors.iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use std::cell::RefCell;
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
@ -6,6 +6,7 @@ mod point;
|
|||||||
pub use point::*;
|
pub use point::*;
|
||||||
mod space_colonization;
|
mod space_colonization;
|
||||||
pub use space_colonization::*;
|
pub use space_colonization::*;
|
||||||
|
mod math;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -16,13 +17,13 @@ extern "C" {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Attractor {
|
pub struct Attractor {
|
||||||
pub position: Point,
|
pub position: Point,
|
||||||
pub dead: bool,
|
pub dead: RefCell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
pub position: Point,
|
pub position: Point,
|
||||||
pub children: RefCell<Vec<Node>>,
|
pub children: RefCell<Vec<Rc<Node>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::hash::Hash for Node {
|
impl std::hash::Hash for Node {
|
||||||
@ -32,7 +33,10 @@ impl std::hash::Hash for Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
pub fn render<F>(&self, render_id: f64, render_fn: F) where F: Copy + Fn(&Node, &Node) {
|
pub fn render<F>(&self, render_id: f64, render_fn: F)
|
||||||
|
where
|
||||||
|
F: Copy + Fn(&Node, &Node),
|
||||||
|
{
|
||||||
let children = self.children.borrow();
|
let children = self.children.borrow();
|
||||||
for child in children.iter() {
|
for child in children.iter() {
|
||||||
render_fn(self, &child);
|
render_fn(self, &child);
|
||||||
@ -42,6 +46,11 @@ impl Node {
|
|||||||
child.render(render_id, render_fn);
|
child.render(render_id, render_fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new(position: Point) -> Self {
|
||||||
|
Self {
|
||||||
|
position,
|
||||||
|
children: Vec::new().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||||
pub struct Point {
|
pub struct Point {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
@ -7,11 +9,11 @@ pub struct Point {
|
|||||||
impl Point {
|
impl Point {
|
||||||
pub fn distance(&self, p: &Point) -> f64 {
|
pub fn distance(&self, p: &Point) -> f64 {
|
||||||
if self.x == p.x {
|
if self.x == p.x {
|
||||||
return (p.y - self.y).into();
|
return (p.y - self.y).abs().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.y == p.y {
|
if self.y == p.y {
|
||||||
return (p.x - self.x).into();
|
return (p.x - self.x).abs().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let x_distance = p.x - self.x;
|
let x_distance = p.x - self.x;
|
||||||
@ -19,6 +21,29 @@ impl Point {
|
|||||||
|
|
||||||
return f64::from(x_distance.pow(2) + y_distance.pow(2)).sqrt();
|
return f64::from(x_distance.pow(2) + y_distance.pow(2)).sqrt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new(positions: (i32, i32)) -> Self {
|
||||||
|
Self {
|
||||||
|
x: positions.0,
|
||||||
|
y: positions.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn movement(&self, towards: Point, distance: u16) -> Point {
|
||||||
|
let dst = self.distance(&towards);
|
||||||
|
if dst == distance as f64 {
|
||||||
|
return towards;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ratio = distance as f64 / dst;
|
||||||
|
info!("X delta : {}", towards.x - self.x);
|
||||||
|
info!("Y delta : {}", towards.y - self.y);
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: ((towards.x - self.x) as f64 * ratio + self.x as f64).round() as i32,
|
||||||
|
y: ((towards.y - self.y) as f64 * ratio + self.y as f64).round() as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -51,30 +76,29 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn distance_same_y_is_x() {
|
fn distance_same_y_is_x() {
|
||||||
let p1 = Point {
|
let p1 = Point::new((1,1));
|
||||||
x: 1,
|
let p2 = Point::new((5,1));
|
||||||
y: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let p2 = Point {
|
|
||||||
x: 5,
|
|
||||||
y: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(p1.distance(&p2), 4.0);
|
assert_eq!(p1.distance(&p2), 4.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn distance_3_4_5() {
|
fn distance_3_4_5() {
|
||||||
let p1 = Point {
|
let p1 = Point::new((0,0));
|
||||||
x: 0,
|
let p2 = Point::new((3,4));
|
||||||
y: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let p2 = Point {
|
assert_eq!(p1.distance(&p2), 5.0);
|
||||||
x: 3,
|
}
|
||||||
y: 4,
|
|
||||||
};
|
#[test]
|
||||||
|
fn distance_is_always_positive() {
|
||||||
|
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));
|
||||||
|
|
||||||
assert_eq!(p1.distance(&p2), 5.0);
|
assert_eq!(p1.distance(&p2), 5.0);
|
||||||
}
|
}
|
||||||
@ -138,5 +162,52 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(p1.distance(&p2) as f32, 71.5891);
|
assert_eq!(p1.distance(&p2) as f32, 71.5891);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scale_does_nothing_when_right_length() {
|
||||||
|
let root = Point::new((0,0));
|
||||||
|
let node = Point::new((0,1));
|
||||||
|
assert_eq!(root.movement(node.clone(), 1), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scale_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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scale_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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scale_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, 5), Point::new((13,14)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scale_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((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)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
use std::cell::RefCell;
|
use super::math::calculate_new_node_position;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use super::{Attractor, Node, Point};
|
use super::{Attractor, Node, Point};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::Rc;
|
||||||
use web_sys::console;
|
use web_sys::console;
|
||||||
use web_sys::window;
|
use web_sys::window;
|
||||||
|
|
||||||
@ -15,24 +15,46 @@ pub struct SpaceColonization {
|
|||||||
/// Maximum distance between an attractor and a node for the node to
|
/// Maximum distance between an attractor and a node for the node to
|
||||||
/// be affected by the attractor.
|
/// be affected by the attractor.
|
||||||
///
|
///
|
||||||
/// Must be greater than sqrt((density)**2 + (density)**2)
|
/// Must be greater than 2 * sqrt((density)**2 + (density)**2) + kill_distance
|
||||||
|
/// The edge case that must be covered is :
|
||||||
|
///
|
||||||
|
/// - There are two attractors and one node like this :
|
||||||
|
/// ```text
|
||||||
|
/// ---------------
|
||||||
|
/// |A | |
|
||||||
|
/// | | |
|
||||||
|
/// | | |
|
||||||
|
/// ---------------
|
||||||
|
/// | | |
|
||||||
|
/// | | |
|
||||||
|
/// | | A|
|
||||||
|
/// --------------- N
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - An attractor is at the top-left corner of its cell
|
||||||
|
/// - The other attractor is at the bottom-right corner of its cell
|
||||||
|
/// - The Node is placed at the bottom-right limit of the kill distance of the bottom-right
|
||||||
|
/// attractor
|
||||||
|
/// - On the next iteration, the bottom-right attractor will be dead so the nearest attractor
|
||||||
|
/// is ( 2 * cell\_diagonal + kill\_distance ) away.
|
||||||
attraction_distance: i32,
|
attraction_distance: i32,
|
||||||
segment_length: i32,
|
segment_length: u16,
|
||||||
/// Size of the cells on which attractors are placed.
|
/// Size of the cells on which attractors are placed.
|
||||||
///
|
///
|
||||||
/// If density is 10, then there will be an average distance of 10 between attractors
|
/// If density is 10, then there will be an average distance of 10 between attractors
|
||||||
density: i32,
|
density: i32,
|
||||||
pub root_nodes: Rc<RefCell<Vec<Node>>>,
|
new_nodes: RefCell<Vec<(Rc<Node>, Rc<Node>)>>,
|
||||||
|
pub root_nodes: RefCell<Vec<Rc<Node>>>,
|
||||||
pub attractors: Rc<RefCell<Vec<Attractor>>>,
|
pub attractors: Rc<RefCell<Vec<Attractor>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpaceColonization {
|
impl SpaceColonization {
|
||||||
pub fn new(width: i32, height: i32) -> SpaceColonization {
|
pub fn new(width: i32, height: i32) -> SpaceColonization {
|
||||||
let root_nodes = Rc::new(RefCell::new(Vec::new()));
|
let root_nodes = RefCell::new(Vec::new());
|
||||||
root_nodes.borrow_mut().push(Node {
|
root_nodes.borrow_mut().push(Rc::new(Node {
|
||||||
position: Point { x: 100, y: 100 },
|
position: Point { x: 100, y: 100 },
|
||||||
children: RefCell::new(Vec::new()),
|
children: RefCell::new(Vec::new()),
|
||||||
});
|
}));
|
||||||
let attractors = Rc::new(RefCell::new(Vec::new()));
|
let attractors = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
|
||||||
let mut sc = SpaceColonization {
|
let mut sc = SpaceColonization {
|
||||||
@ -41,11 +63,12 @@ impl SpaceColonization {
|
|||||||
y: height,
|
y: height,
|
||||||
},
|
},
|
||||||
kill_distance: 10,
|
kill_distance: 10,
|
||||||
attraction_distance: 43,
|
attraction_distance: 430,
|
||||||
segment_length: 5,
|
segment_length: 50,
|
||||||
density: 30,
|
density: 300,
|
||||||
root_nodes,
|
root_nodes,
|
||||||
attractors,
|
attractors,
|
||||||
|
new_nodes: RefCell::new(Vec::new()),
|
||||||
};
|
};
|
||||||
|
|
||||||
sc.place_attractors();
|
sc.place_attractors();
|
||||||
@ -71,7 +94,7 @@ impl SpaceColonization {
|
|||||||
while y_pos < self.max_point.y {
|
while y_pos < self.max_point.y {
|
||||||
self.attractors.borrow_mut().push(Attractor {
|
self.attractors.borrow_mut().push(Attractor {
|
||||||
position: self.get_random_point(x_pos.into(), y_pos.into()),
|
position: self.get_random_point(x_pos.into(), y_pos.into()),
|
||||||
dead: false,
|
dead: RefCell::new(false),
|
||||||
});
|
});
|
||||||
y_pos += self.density;
|
y_pos += self.density;
|
||||||
}
|
}
|
||||||
@ -107,32 +130,44 @@ impl SpaceColonization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grow(&mut self) {
|
pub fn grow(&self) {
|
||||||
|
self.grow_nodes(&self.root_nodes);
|
||||||
|
for new_pair in self.new_nodes.borrow().iter() {
|
||||||
|
new_pair.0.children.borrow_mut().push(new_pair.1.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grow_nodes(&self, nodes: &RefCell<Vec<Rc<Node>>>) {
|
||||||
// iterate through attractors
|
// iterate through attractors
|
||||||
// find closest node within attraction range
|
// find closest node within attraction range
|
||||||
// build a map of nodes to affecting attractors
|
// build a map of nodes to affecting attractors
|
||||||
// attractors within the attraction range that this node is the closest to
|
// attractors within the attraction range that this node is the closest to
|
||||||
//
|
//
|
||||||
// calculate new node position
|
// calculate new node position
|
||||||
let root_nodes = self.root_nodes.borrow();
|
let nodes = nodes.borrow();
|
||||||
let attractors = self.attractors.borrow();
|
let attractors = self.attractors.borrow();
|
||||||
let mut growing_paths : HashMap<&Node, Vec<&Attractor>> = HashMap::new();
|
let mut growing_paths: HashMap<Rc<Node>, Vec<&Attractor>> = HashMap::new();
|
||||||
for a in attractors.iter() {
|
for a in attractors.iter() {
|
||||||
let mut closest_node: Option<&Node> = None;
|
let a_dead_mut = a.dead.borrow();
|
||||||
|
if *a_dead_mut {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut closest_node: Option<Rc<Node>> = None;
|
||||||
let mut closest_node_distance = f64::MAX;
|
let mut closest_node_distance = f64::MAX;
|
||||||
|
|
||||||
for n in root_nodes.iter() { // TODO iterate on children nodes
|
for n in nodes.iter() {
|
||||||
|
// TODO iterate on children nodes
|
||||||
let distance = n.position.distance(&a.position);
|
let distance = n.position.distance(&a.position);
|
||||||
if distance <= self.attraction_distance as f64 {
|
if distance <= self.attraction_distance as f64 {
|
||||||
console::log_1(&format!("found node within attraction distance {:?}", a).into());
|
|
||||||
if distance < closest_node_distance {
|
if distance < closest_node_distance {
|
||||||
closest_node = Some(n);
|
closest_node = Some(n.clone());
|
||||||
closest_node_distance = distance;
|
closest_node_distance = distance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.grow_nodes(&n.children);
|
||||||
}
|
}
|
||||||
if let Some(node) = closest_node {
|
if let Some(node) = closest_node {
|
||||||
if let Some(attractors) = growing_paths.get_mut(node) {
|
if let Some(attractors) = growing_paths.get_mut(&node) {
|
||||||
attractors.push(a);
|
attractors.push(a);
|
||||||
} else {
|
} else {
|
||||||
let mut attractors = Vec::new();
|
let mut attractors = Vec::new();
|
||||||
@ -141,10 +176,14 @@ impl SpaceColonization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console::log_1(&format!("found {:?} pairs ", growing_paths).into());
|
for growth_cell in growing_paths {
|
||||||
for node in growing_paths {
|
let position = calculate_new_node_position(&growth_cell, self.segment_length);
|
||||||
todo!("calculate new node position averaging all attractors");
|
for a in growth_cell.1 {
|
||||||
|
if position.distance(&a.position) < self.kill_distance as f64 {
|
||||||
|
a.dead.replace(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.new_nodes.borrow_mut().push((growth_cell.0.clone(), Rc::new(Node::new(position))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user