feat(space colonization): now pretty much works! still slowish but not too bad, about 10fps at worst on a good laptop with a ryzen 6800u

This commit is contained in:
jeangab 2023-08-10 22:05:46 -04:00
parent 62208591fc
commit 97cf5b21d9
5 changed files with 284 additions and 40 deletions

41
Cargo.lock generated
View File

@ -735,6 +735,19 @@ dependencies = [
"syn 2.0.28", "syn 2.0.28",
] ]
[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.2" version = "0.3.2"
@ -1069,6 +1082,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.27" version = "0.14.27"
@ -1427,6 +1446,7 @@ dependencies = [
"cfg-if", "cfg-if",
"console_error_panic_hook", "console_error_panic_hook",
"console_log", "console_log",
"env_logger",
"getrandom", "getrandom",
"js-sys", "js-sys",
"leptos", "leptos",
@ -1436,6 +1456,7 @@ dependencies = [
"log", "log",
"rand", "rand",
"simple_logger", "simple_logger",
"test-log",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
] ]
@ -2349,6 +2370,26 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "test-log"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9601d162c1d77e62c1ea0bc8116cd1caf143ce3af947536c3c9052a1677fe0c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.44" version = "1.0.44"

View File

@ -96,3 +96,7 @@ lib-features = ["hydrate"]
# #
# Optional. Defaults to false. # Optional. Defaults to false.
lib-default-features = false lib-default-features = false
[dev-dependencies]
test-log = "*"
env_logger = "*"

View File

@ -59,10 +59,6 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
sc.render_nodes(&nodes.borrow(), render_id, render_node_fn); sc.render_nodes(&nodes.borrow(), render_id, render_node_fn);
context.stroke(); context.stroke();
context.set_fill_style(&JsValue::from("magenta"));
for a in sc.attractors.iter() {
context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0);
}
let end_time = window().unwrap().performance().unwrap().now(); let end_time = window().unwrap().performance().unwrap().now();
log!( log!(
"Rendering nodes and {} attractors took {}", "Rendering nodes and {} attractors took {}",
@ -80,6 +76,15 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let render_id = window().unwrap().performance().unwrap().now(); let render_id = window().unwrap().performance().unwrap().now();
context.begin_path(); context.begin_path();
sc.render_nodes(&nodes.borrow(), render_id, render_node_fn); sc.render_nodes(&nodes.borrow(), 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(); context.stroke();
let end_time = window().unwrap().performance().unwrap().now(); let end_time = window().unwrap().performance().unwrap().now();
log!( log!(
@ -90,7 +95,9 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
}); });
let window = window().unwrap(); let window = window().unwrap();
window.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()).unwrap(); window
.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
.unwrap();
closure.forget(); closure.forget();
}); });

View File

@ -1,26 +1,107 @@
use log::info;
use super::{Attractor, Node, Point}; use super::{Attractor, Node, Point};
pub fn calculate_new_node_position( pub fn calculate_new_node_position(
node: &Node, node: &Node,
attractors: &Vec<*mut Attractor>, attractors: &Vec<*mut Attractor>,
segment_length: u16, segment_length: u16,
) -> Point { ) -> 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_x = 0;
let mut attraction_sum_y = 0; let mut attraction_sum_y = 0;
for a in attractors.iter() { for a in attractors.iter().skip(attractors_to_skip) {
unsafe { if attractors_to_skip > 0 {
attraction_sum_x += (**a).position.x - node.position.x; info!("working on attractor {:?}", a);
attraction_sum_y += (**a).position.y - node.position.y;
} }
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 { let point = Point {
x: node.position.x + attraction_sum_x / attractors.len() as i32, x: node.position.x + attraction_sum_x as i32,
y: node.position.y + attraction_sum_y / attractors.len() 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)] #[cfg(test)]
@ -30,19 +111,97 @@ mod tests {
#[test] #[test]
fn new_node_moves_toward_single_attractor() { fn new_node_moves_toward_single_attractor() {
let mut growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec()); let mut growth_cell = GrowthCell::from_positions((0, 0), [(0, 10)].to_vec());
let attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut(); let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut();
let point = calculate_new_node_position( let point = calculate_new_node_position(
&growth_cell.node, &growth_cell.node,
&attractors_as_ptr_mut, &mut attractors_as_ptr_mut,
SEGMENT_LENGTH, SEGMENT_LENGTH,
); )
.unwrap();
assert_eq!(point, Point::new((0, 5))); assert_eq!(point, Point::new((0, 5)));
} }
#[test] #[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 { struct GrowthCell {
node: Node, node: Node,
@ -50,15 +209,15 @@ mod tests {
} }
impl GrowthCell { impl GrowthCell {
pub fn from_positions(positions: Vec<(i32, i32)>) -> Self { pub fn from_positions(node: (i32, i32), attractors_positions: Vec<(i32, i32)>) -> Self {
assert!(positions.len() >= 2); assert!(attractors_positions.len() >= 1);
let node = Node { let node = Node {
position: Point::new(positions[0]), position: Point::new(node),
children: Vec::new().into(), children: Vec::new().into(),
growing: true, growing: true,
}; };
let mut attractors = Vec::new(); let mut attractors = Vec::new();
for p in positions.iter().skip(1) { for p in attractors_positions.iter() {
attractors.push(Attractor { attractors.push(Attractor {
position: Point::new(*p), position: Point::new(*p),
dead: false, dead: false,
@ -68,7 +227,10 @@ mod tests {
} }
fn attractors_as_ptr_mut(&mut self) -> Vec<*mut Attractor> { fn attractors_as_ptr_mut(&mut self) -> Vec<*mut Attractor> {
self.attractors.iter_mut().map(|a| { a as *mut Attractor }).collect() self.attractors
.iter_mut()
.map(|a| a as *mut Attractor)
.collect()
} }
} }
} }

View File

@ -139,6 +139,7 @@ impl SpaceColonization {
// - I can efficiently render my nodes on a canvas // - I can efficiently render my nodes on a canvas
// - I use as little memory as possible // - I use as little memory as possible
// - I can update my nodes // - I can update my nodes
info!("Growing nodes {:?}", nodes);
self.grow_nodes(&mut nodes) self.grow_nodes(&mut nodes)
} }
@ -189,20 +190,29 @@ impl SpaceColonization {
// //
// Using raw fixed length arrays would solve that but its a fine line between too // Using raw fixed length arrays would solve that but its a fine line between too
// large memory usage and enough children nodes // 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 { unsafe {
let new_node = Node::new(calculate_new_node_position( /*
&(**node), let segfault_boy: *const u8 = usize::MAX as *const u8;
attractor, let memory_content = *segfault_boy;
self.segment_length, println!("memory content at address 0 {}", memory_content);
)); */
attractor.iter().for_each(|a| { let new_point =
if (**a).position.distance(&new_node.position) <= self.kill_distance { calculate_new_node_position(&(**node), attractor, self.segment_length);
dead_attractors.push(*a); if let Some(new_point) = new_point {
(**a).dead = true; let new_node = Node::new(new_point);
} attractor.iter().for_each(|a| {
}); if (**a).position.distance(&new_node.position) <= self.kill_distance {
(**node).children.push(new_node); dead_attractors.push(*a);
(**a).dead = true;
}
});
(**node).children.push(new_node);
}
} }
}); });
} }
@ -212,6 +222,10 @@ impl SpaceColonization {
mut attractor_to_closest_node: &mut HashMap<*mut Attractor, Attraction>, mut attractor_to_closest_node: &mut HashMap<*mut Attractor, Attraction>,
n: &mut Node, 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 { if !n.growing {
return; return;
} }
@ -233,14 +247,11 @@ impl SpaceColonization {
} }
} }
for child in n.children.iter_mut() {
self.build_attractor_to_closest_node(&mut attractor_to_closest_node, child);
}
} }
fn find_attractors_in_range(&mut self, n: &Node) -> Vec<(*mut Attractor, f64)> { fn find_attractors_in_range(&mut self, n: &Node) -> Vec<(*mut Attractor, f64)> {
let mut attractors_in_range = Vec::new(); let mut attractors_in_range = Vec::new();
for a in self.attractors.iter_mut() { for a in self.attractors.iter_mut().filter(|a| !a.dead) {
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 {
attractors_in_range.push((a as *mut Attractor, distance)); attractors_in_range.push((a as *mut Attractor, distance));
@ -304,7 +315,6 @@ mod test {
println!("after grow 2"); println!("after grow 2");
dbg!(&nodes); dbg!(&nodes);
// TODO assert nodes 3,0 and 6,0
assert_vertices( assert_vertices(
&sc, &sc,
&nodes, &nodes,
@ -323,4 +333,24 @@ mod test {
1 1
); );
} }
#[test]
fn grow_should_ignore_dead_attractors() {
let mut nodes = Vec::new();
nodes.push(Node::new(Point::new((0, 0))));
let mut attractors = Vec::new();
attractors.push(Attractor {
position: Point::new((10, 0)),
dead: true,
});
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors);
assert_eq!(sc.attractors.len(), 1);
assert!(sc.attractors.iter().find(|a| a.dead == true).is_some());
sc.grow(&mut nodes);
assert_vertices(&sc, &nodes, Vec::new());
}
} }