diff --git a/src/kernel_test.rs b/src/kernel_test.rs index 71cfaab..62013a2 100644 --- a/src/kernel_test.rs +++ b/src/kernel_test.rs @@ -7,12 +7,12 @@ mod random; mod walker; use std::f64::consts::SQRT_2; - +use std::process::exit; use crate::{editor::*, fps_control::*, grid_render::*, map::*, position::*, random::*, walker::*}; use egui::emath::Numeric; -use egui::{Label}; +use egui::Label; use macroquad::color::*; use macroquad::shapes::*; use macroquad::window::clear_background; @@ -156,81 +156,31 @@ async fn main() { outer_size: 5, }; - Kernel::evaluate_kernels(19); + let (all_valid_radii_sqr, max_inner_radius_for_outer, max_outer_radius_for_inner) = + Kernel::precompute_kernel_configurations(19); loop { fps_ctrl.on_frame_start(); editor.on_frame_start(); - define_egui(&mut editor, &mut state); - editor.set_cam(&map); editor.handle_user_inputs(&map); - clear_background(GRAY); draw_walker(&walker); - walker.kernel = Kernel::new(state.outer_size, state.outer_radius_sqr); + let outer_kernel = Kernel::new(state.outer_size, state.outer_radius_sqr); + walker.kernel = outer_kernel.clone(); draw_thingy(&walker, false); - let weird_factor: f64 = 2.0 * SQRT_2 * state.outer_radius_sqr.to_f64().sqrt(); - let max_inner_radius_sqr: f64 = state.outer_radius_sqr.to_f64() - weird_factor + 2.0; - // as this is based on a less or equal equation we can round down to the next integer, - let valid_inner_radius_sqr: usize = (max_inner_radius_sqr).round().to_usize().unwrap(); - - // NOTE: it seems to work with a crappy fix like this using +0.2 ... this is the case - // because an extra distance of sqrt(2) is required in the "worst case" if the - // "most outward" blocks are exactly on the limiting radius (e.g. max radius). Then, The full - // sqrt(2) are required, in other cases less is okay. Therefore the sqrt(2) assumption - // might not be that useful? What other possible ways could there be to validate if outer - // kernel has at least "one padding" around the inner kernel? - - // TODO: idea: dont do radius+sqrt(2), but radius-unused+sqrt(2), where unused is the - // amount of the radius that is not required for active blocks. This means i need to - // somehow get the "actual limiting radius" - - // NOTE: okay jesus christ im going insane. it turns out that this entire approach is - // faulty to begin with. When only using kernel-radii that are exactly limiting the most - // outter blocks, i expected the remaining margin to be sqrt(2) (see get_unique_radii_sqr). - // But, it turns out that this is not correct. Imagine 3x3 - 4x4. Then yes. but imagine - // 2x3 - 3x4, then yes the difference vector is (1,1) which has a distance of sqrt(2). But - // if you draw the whole thing using vectors you'll see that the distance between the radii - // and the limited blocks is only both equal to sqrt(2) if the vectors for the limiting - // are overlapping. I tried to think about doing funky calculations using angles, but the - // inner circle is not known and therefore the angle is also not known. - // Instead i came up with a completly new Kernel representation, which would make this - // entire calculation obsolete. Instead of defining some circularity/radius i could define - // the 'limiting block' (x, y), which would also implicitly define a radius. I also should ensure - // that x and y are positive. Then, i could simply reduce -1 from both and have the 'one - // smaller but valid' kernel! It might also make sense to have some x>=y constraint because - // otherwise (3,2) and (2,3) would result in the same kernel. - // This idea could also have some problems that im currently not noticing :D - // (x,y) are actually the offset to the center, so the largest possible value resulting in - // a square kernel would be (size - center - 1, size - center - 1) and the smallest possible value - // would be (0, size - center - 1), which should result in fully circular kernel. - // the only downside i can think of is that, while i can calculate a circularity (0 - 1) - // based on these informations, i cant generate a kernel based on some circularity in some - // trivial way. This would be nice when changing the size of the kernel, but wanting that - // it remains a similar shape. - - if state.inner_radius_sqr as f64 > max_inner_radius_sqr { - state.inner_radius_sqr = valid_inner_radius_sqr; - } - - walker.kernel = Kernel::new(state.inner_size, state.inner_radius_sqr); - - // let valid_radii_sqr = Kernel::get_unique_radii_sqr(state.inner_size); - - // dbg!(( - // &weird_factor, - // &max_inner_radius_sqr, - // &valid_inner_radius_sqr, - // &state, - // &walker.kernel, - // &valid_radii_sqr - // )); + let inner_kernel = Kernel::new(state.inner_size, state.inner_radius_sqr); + walker.kernel = inner_kernel.clone(); draw_thingy(&walker, true); + let max_valid_inner_radius = max_inner_radius_for_outer.get(&state.outer_radius_sqr); + if state.inner_radius_sqr > *max_valid_inner_radius.unwrap() { + dbg!("invalid", &state.inner_radius_sqr, &max_valid_inner_radius); + } + egui_macroquad::draw(); fps_ctrl.wait_for_next_frame().await; } diff --git a/src/map.rs b/src/map.rs index 0c45756..870e465 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,7 +1,5 @@ use std::f64::consts::SQRT_2; - - use crate::CuteWalker; use crate::Position; use egui::emath::Numeric; @@ -29,7 +27,7 @@ pub struct Map { pub width: usize, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Kernel { pub size: usize, pub radius_sqr: usize, @@ -38,6 +36,7 @@ pub struct Kernel { impl Kernel { pub fn new(size: usize, radius_sqr: usize) -> Kernel { + assert!(size % 2 == 1, "kernel size must be odd"); let vector = Kernel::get_kernel_vector(size, radius_sqr); Kernel { size, @@ -50,10 +49,16 @@ impl Kernel { (size - 1) / 2 } + fn center(&self) -> usize { + (self.size - 1) / 2 + } + + fn max_offset(&self) -> usize { + (self.size - 1) / 2 + } + pub fn get_valid_radius_bounds(size: usize) -> (usize, usize) { - // TODO: center and min_radius are actually the same value let center = Kernel::get_kernel_center(size); - let min_radius = ((size - 1) / 2).pow(2); // min radius is from center to border let max_radius = center * center + center * center; // max radius is from center to corner @@ -62,28 +67,10 @@ impl Kernel { pub fn is_valid_radius(size: usize, radius_sqr: usize) -> bool { let (min_radius, max_radius) = Kernel::get_valid_radius_bounds(size); - min_radius <= radius_sqr && radius_sqr <= max_radius } - // pub fn get_min_circularity(size: usize, radius_limit: f32) -> f32 { - // let center = Kernel::get_kernel_center(size); - // - // let min_radius = (size - 1) as f32 / 2.0; - // let max_radius = f32::sqrt(center * center + center * center); - // - // let actual_max_radius = f32::min(max_radius, radius_limit); // get LOWER bound - // - // // calculate circularity which results in actual max radius by linear combination of min - // // and max radius - // // a=xb+(1-x)c => x = (a-c)/(b-c) - // - // let min_circularity = (actual_max_radius - max_radius) / (min_radius - max_radius); - // - // min_circularity - // } - /// TODO: this could also be further optimized by using the kernels symmetry, but instead of /// optimizing this function it would make sense to replace the entire kernel fn get_kernel_vector(size: usize, radius_sqr: usize) -> Array2 { @@ -126,11 +113,42 @@ impl Kernel { valid_sqr_distances } - pub fn evaluate_kernels(max_kernel_size: usize) { - let all_valid_radii_sqr = Kernel::get_unique_radii_sqr(max_kernel_size, false); + pub fn check_kernel_configuration(inner_kernel: &Kernel, outer_kernel: &Kernel) -> bool { + let max_offset = inner_kernel.max_offset(); + let inner_center = inner_kernel.center(); + let outer_center = outer_kernel.center(); - // TODO: use two hashmaps to achieve bidirectional mapping, not sure if i actually need - // this, but might come in handy + for x in (0..=max_offset).rev() { + for y in (0..=max_offset).rev() { + let inner_pos = (inner_center + x, inner_center + y); + if inner_kernel.vector.get(inner_pos) != Some(&true) { + continue; // inner cell is not active, skip + } + + // check adjacent neighboring cells in outer kernel + for &offset in &[(1, 1), (0, 1), (0, 1)] { + let outer_pos = (outer_center + x + offset.0, outer_center + y + offset.1); + if outer_kernel.vector.get(outer_pos) != Some(&true) { + return false; // outer cell is not active -> INVALID + } + } + } + } + + true + } + + /// Precomputes all possible unique squared radii that can be used and their compatibility + /// when used as inner and outer kernels. The algorithm that checks the validity is somewhat + /// inefficient, so this function should be called once at startup, but not used at runtime. + /// TODO: currently this returns a bidirectional mapping using two hashmaps. Do i actually need + /// that? + /// TODO: precomputed information could be encapsulated inside a struct and easily accessed + /// using functions, hiding all that pain + pub fn precompute_kernel_configurations( + max_kernel_size: usize, + ) -> (Vec, HashMap, HashMap) { + let all_valid_radii_sqr = Kernel::get_unique_radii_sqr(max_kernel_size, false); let mut max_inner_radius_for_outer: HashMap = HashMap::new(); let mut max_outer_radius_for_inner: HashMap = HashMap::new(); @@ -140,11 +158,13 @@ impl Kernel { for inner_radius_index in (0..outer_radius_index).rev() { let inner_radius = *all_valid_radii_sqr.get(inner_radius_index).unwrap(); - // validate if inner radius is valid TODO: replace this with an error free method! - let factor: f64 = 2.0 * SQRT_2 * outer_radius.to_f64().sqrt(); - let kernel_is_valid = inner_radius.to_f64() <= outer_radius.to_f64() - factor + 2.0; + // validate if inner radius is valid + let inner_kernel = Kernel::new(max_kernel_size, inner_radius); + let outer_kernel = Kernel::new(max_kernel_size, outer_radius); + let kernel_valid = Kernel::check_kernel_configuration(&inner_kernel, &outer_kernel); - if kernel_is_valid { + // if it is optimal, store it as the upper bound and skip all possible smaller ones + if kernel_valid { println!("outer: {:} \t inner: {:}", outer_radius, inner_radius); max_inner_radius_for_outer.insert(outer_radius, inner_radius); // always unique entry max_outer_radius_for_inner.insert(inner_radius, outer_radius); // will override @@ -153,8 +173,11 @@ impl Kernel { } } - dbg!(max_inner_radius_for_outer); - dbg!(max_outer_radius_for_inner); + ( + all_valid_radii_sqr, + max_inner_radius_for_outer, + max_outer_radius_for_inner, + ) } }