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",
]
[[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]]
name = "errno"
version = "0.3.2"
@ -1069,6 +1082,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.27"
@ -1427,6 +1446,7 @@ dependencies = [
"cfg-if",
"console_error_panic_hook",
"console_log",
"env_logger",
"getrandom",
"js-sys",
"leptos",
@ -1436,6 +1456,7 @@ dependencies = [
"log",
"rand",
"simple_logger",
"test-log",
"wasm-bindgen",
"web-sys",
]
@ -2349,6 +2370,26 @@ dependencies = [
"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]]
name = "thiserror"
version = "1.0.44"

View File

@ -96,3 +96,7 @@ lib-features = ["hydrate"]
#
# Optional. Defaults to 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);
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();
log!(
"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();
context.begin_path();
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();
let end_time = window().unwrap().performance().unwrap().now();
log!(
@ -90,7 +95,9 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
});
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();
});

View File

@ -1,26 +1,107 @@
use log::info;
use super::{Attractor, Node, Point};
pub fn calculate_new_node_position(
node: &Node,
attractors: &Vec<*mut Attractor>,
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_y = 0;
for a in attractors.iter() {
unsafe {
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)]
@ -30,19 +111,97 @@ mod tests {
#[test]
fn new_node_moves_toward_single_attractor() {
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 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,
&attractors_as_ptr_mut,
&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,
@ -50,15 +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,
@ -68,7 +227,10 @@ mod tests {
}
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 use as little memory as possible
// - I can update my nodes
info!("Growing nodes {:?}", nodes);
self.grow_nodes(&mut nodes)
}
@ -189,13 +190,21 @@ impl SpaceColonization {
//
// 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_node = Node::new(calculate_new_node_position(
&(**node),
attractor,
self.segment_length,
));
/*
let segfault_boy: *const u8 = usize::MAX as *const u8;
let memory_content = *segfault_boy;
println!("memory content at address 0 {}", memory_content);
*/
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);
@ -204,6 +213,7 @@ impl SpaceColonization {
});
(**node).children.push(new_node);
}
}
});
}
@ -212,6 +222,10 @@ impl SpaceColonization {
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;
}
@ -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)> {
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);
if distance < self.attraction_distance as f64 {
attractors_in_range.push((a as *mut Attractor, distance));
@ -304,7 +315,6 @@ mod test {
println!("after grow 2");
dbg!(&nodes);
// TODO assert nodes 3,0 and 6,0
assert_vertices(
&sc,
&nodes,
@ -323,4 +333,24 @@ mod test {
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());
}
}