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:
parent
62208591fc
commit
97cf5b21d9
41
Cargo.lock
generated
41
Cargo.lock
generated
@ -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"
|
||||
|
@ -96,3 +96,7 @@ lib-features = ["hydrate"]
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
lib-default-features = false
|
||||
|
||||
[dev-dependencies]
|
||||
test-log = "*"
|
||||
env_logger = "*"
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user