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
|
||||
);
|
||||
|
||||
for _i in 1..15 {
|
||||
for _i in 1..5 {
|
||||
sc.grow();
|
||||
let render_id = window().unwrap().performance().unwrap().now();
|
||||
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::*;
|
||||
|
||||
@ -6,6 +6,7 @@ mod point;
|
||||
pub use point::*;
|
||||
mod space_colonization;
|
||||
pub use space_colonization::*;
|
||||
mod math;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
@ -16,13 +17,13 @@ extern "C" {
|
||||
#[derive(Debug)]
|
||||
pub struct Attractor {
|
||||
pub position: Point,
|
||||
pub dead: bool,
|
||||
pub dead: RefCell<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Node {
|
||||
pub position: Point,
|
||||
pub children: RefCell<Vec<Node>>,
|
||||
pub children: RefCell<Vec<Rc<Node>>>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Node {
|
||||
@ -32,7 +33,10 @@ impl std::hash::Hash for 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();
|
||||
for child in children.iter() {
|
||||
render_fn(self, &child);
|
||||
@ -42,6 +46,11 @@ impl Node {
|
||||
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 x: i32,
|
||||
pub y: i32,
|
||||
@ -7,11 +9,11 @@ pub struct Point {
|
||||
impl Point {
|
||||
pub fn distance(&self, p: &Point) -> f64 {
|
||||
if self.x == p.x {
|
||||
return (p.y - self.y).into();
|
||||
return (p.y - self.y).abs().into();
|
||||
}
|
||||
|
||||
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;
|
||||
@ -19,6 +21,29 @@ impl Point {
|
||||
|
||||
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)]
|
||||
@ -51,30 +76,29 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn distance_same_y_is_x() {
|
||||
let p1 = Point {
|
||||
x: 1,
|
||||
y: 1,
|
||||
};
|
||||
|
||||
let p2 = Point {
|
||||
x: 5,
|
||||
y: 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 {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
let p1 = Point::new((0,0));
|
||||
let p2 = Point::new((3,4));
|
||||
|
||||
let p2 = Point {
|
||||
x: 3,
|
||||
y: 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));
|
||||
|
||||
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);
|
||||
}
|
||||
@ -138,5 +162,52 @@ mod tests {
|
||||
|
||||
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 std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::math::calculate_new_node_position;
|
||||
use super::{Attractor, Node, Point};
|
||||
use rand::thread_rng;
|
||||
use rand::Rng;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use web_sys::console;
|
||||
use web_sys::window;
|
||||
|
||||
@ -15,24 +15,46 @@ pub struct SpaceColonization {
|
||||
/// 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)
|
||||
/// 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,
|
||||
segment_length: i32,
|
||||
segment_length: u16,
|
||||
/// 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: 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>>>,
|
||||
}
|
||||
|
||||
impl SpaceColonization {
|
||||
pub fn new(width: i32, height: i32) -> SpaceColonization {
|
||||
let root_nodes = Rc::new(RefCell::new(Vec::new()));
|
||||
root_nodes.borrow_mut().push(Node {
|
||||
let root_nodes = RefCell::new(Vec::new());
|
||||
root_nodes.borrow_mut().push(Rc::new(Node {
|
||||
position: Point { x: 100, y: 100 },
|
||||
children: RefCell::new(Vec::new()),
|
||||
});
|
||||
}));
|
||||
let attractors = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
let mut sc = SpaceColonization {
|
||||
@ -41,11 +63,12 @@ impl SpaceColonization {
|
||||
y: height,
|
||||
},
|
||||
kill_distance: 10,
|
||||
attraction_distance: 43,
|
||||
segment_length: 5,
|
||||
density: 30,
|
||||
attraction_distance: 430,
|
||||
segment_length: 50,
|
||||
density: 300,
|
||||
root_nodes,
|
||||
attractors,
|
||||
new_nodes: RefCell::new(Vec::new()),
|
||||
};
|
||||
|
||||
sc.place_attractors();
|
||||
@ -71,7 +94,7 @@ impl SpaceColonization {
|
||||
while y_pos < self.max_point.y {
|
||||
self.attractors.borrow_mut().push(Attractor {
|
||||
position: self.get_random_point(x_pos.into(), y_pos.into()),
|
||||
dead: false,
|
||||
dead: RefCell::new(false),
|
||||
});
|
||||
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
|
||||
// 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 nodes = nodes.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() {
|
||||
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;
|
||||
|
||||
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);
|
||||
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 = Some(n.clone());
|
||||
closest_node_distance = distance;
|
||||
}
|
||||
}
|
||||
self.grow_nodes(&n.children);
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
let mut attractors = Vec::new();
|
||||
@ -141,10 +176,14 @@ impl SpaceColonization {
|
||||
}
|
||||
}
|
||||
}
|
||||
console::log_1(&format!("found {:?} pairs ", growing_paths).into());
|
||||
for node in growing_paths {
|
||||
todo!("calculate new node position averaging all attractors");
|
||||
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);
|
||||
}
|
||||
}
|
||||
self.new_nodes.borrow_mut().push((growth_cell.0.clone(), Rc::new(Node::new(position))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user