diff --git a/Cargo.toml b/Cargo.toml index 984020e6..b7b76ce5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = ["poisson", "poisson-visualisation"] +resolver = "2" [profile.test] opt-level = 3 \ No newline at end of file diff --git a/poisson-visualisation/Cargo.toml b/poisson-visualisation/Cargo.toml index 150f5e27..ec24e79e 100644 --- a/poisson-visualisation/Cargo.toml +++ b/poisson-visualisation/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "poisson-visualisation" -version = "0.1.0" +version = "0.11.0" authors = ["WaDelma <>"] -edition = "2018" +edition = "2024" [dependencies] -nalgebra = "0.17" -image = "0.21.0" -clap = "2.32.0" -rand = "0.6" -lab = "0.4" +nalgebra = { version = "0.34.1", features = ["alga", "rand"] } +image = "0.25.8" +clap = "4.5.48" +rand = "0.9.2" +lab = "0.11.0" fnv = "1.0" poisson = { path = "../poisson" } \ No newline at end of file diff --git a/poisson-visualisation/src/main.rs b/poisson-visualisation/src/main.rs index c893d993..e186dfa3 100644 --- a/poisson-visualisation/src/main.rs +++ b/poisson-visualisation/src/main.rs @@ -1,8 +1,12 @@ -use clap::{App, Arg, ArgMatches, arg_enum, _clap_count_exprs, value_t}; +use clap::{builder::PossibleValuesParser, Arg, ArgMatches, Command}; -use poisson::{Builder, Type, algorithm::{Bridson, Ebeida}}; +use poisson::{ + algorithm::{Bridson, Ebeida}, + Builder, Type, +}; -use rand::{rngs::SmallRng, FromEntropy, Rng, seq::SliceRandom, SeedableRng}; +use rand::rngs::SmallRng; +use rand::{rng, seq::SliceRandom, Rng, SeedableRng}; use nalgebra::Vector2; @@ -13,89 +17,125 @@ use lab::Lab; use fnv::FnvHasher; use std::hash::Hasher; +use std::str::FromStr; -arg_enum! { - #[derive(PartialEq, Debug)] - pub enum Algo { - Ebeida, - Bridson +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum Algo { + Ebeida, + Bridson, +} + +impl FromStr for Algo { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "ebeida" => Ok(Algo::Ebeida), + "bridson" => Ok(Algo::Bridson), + _ => Err(format!("Invalid algorithm: {}", s)), + } } } -arg_enum! { - #[derive(PartialEq, Debug)] - pub enum Style { - Plain, - Colorful, - Dot +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum Style { + Plain, + Colorful, + Dot, +} + +impl FromStr for Style { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "plain" => Ok(Style::Plain), + "colorful" => Ok(Style::Colorful), + "dot" => Ok(Style::Dot), + _ => Err(format!("Invalid style: {}", s)), + } } } fn main() { - let app = App::new("Poisson visualisation") + let app = Command::new("Poisson visualisation") .author("delma") .version("0.1.0") .about("Visualisation for poisson library") .arg( - Arg::with_name("OUTPUT") + Arg::new("OUTPUT") .help("Output file that's generated") .required(true) - .index(1) - ) - .arg( - Arg::with_name("SEED") - .help("Seed for the generation") - .index(2) + .index(1), ) + .arg(Arg::new("SEED").help("Seed for the generation").index(2)) .arg( - Arg::with_name("radius") - .short("r") - .takes_value(true) - .help("Radius of the disks") + Arg::new("radius") + .short('r') + .value_name("RADIUS") + .help("Radius of the disks"), ) .arg( - Arg::with_name("width") - .short("w") - .takes_value(true) - .help("Width of the generated image") + Arg::new("width") + .short('w') + .value_name("WIDTH") + .help("Width of the generated image"), ) .arg( - Arg::with_name("height") - .short("h") - .takes_value(true) - .help("Height of the generated image") + Arg::new("height") + .short('h') + .value_name("HEIGHT") + .help("Height of the generated image"), ) .arg( - Arg::with_name("style") - .short("s") - .takes_value(true) + Arg::new("style") + .short('s') + .value_name("STYLE") .help("Style for the disks") - .possible_values(&Style::variants()) + .value_parser(PossibleValuesParser::new(["plain", "colorful", "dot"])), ) .arg( - Arg::with_name("algo") - .short("a") + Arg::new("algo") + .short('a') .help("Algorithm that's used to generate image") - .takes_value(true) - .possible_values(&Algo::variants()) + .value_name("ALGO") + .value_parser(PossibleValuesParser::new(["ebeida", "bridson"])), ); visualise(app.get_matches()); } fn visualise(m: ArgMatches) { - let width = value_t!(m, "width", u32).unwrap_or(1024); - let height = value_t!(m, "height", u32).unwrap_or(1024); - let radius = value_t!(m, "radius", f32).unwrap_or(0.02); - let algo = value_t!(m, "algo", Algo).unwrap_or(Algo::Ebeida); - let style = value_t!(m, "style", Style).unwrap_or(Style::Plain); - let name = m.value_of("OUTPUT").unwrap(); - let master_rng = m.value_of("SEED").map(|s| { - let mut fnv = FnvHasher::with_key(0); - for b in s.bytes() { - fnv.write_u8(b); - } - SmallRng::seed_from_u64(fnv.finish()) - }).unwrap_or_else(SmallRng::from_entropy); + let width: u32 = m + .get_one::("width") + .and_then(|s| s.parse().ok()) + .unwrap_or(1024); + let height: u32 = m + .get_one::("height") + .and_then(|s| s.parse().ok()) + .unwrap_or(1024); + let radius: f32 = m + .get_one::("radius") + .and_then(|s| s.parse().ok()) + .unwrap_or(0.02); + let algo = m + .get_one::("algo") + .and_then(|s| Algo::from_str(s).ok()) + .unwrap_or(Algo::Ebeida); + let style = m + .get_one::("style") + .and_then(|s| Style::from_str(s).ok()) + .unwrap_or(Style::Plain); + let name = m.get_one::("OUTPUT").expect("OUTPUT argument is required"); + let master_rng = m + .get_one::("SEED") + .map(|s| { + let mut fnv = FnvHasher::with_key(0); + for b in s.bytes() { + fnv.write_u8(b); + } + SmallRng::seed_from_u64(fnv.finish()) + }) + .unwrap_or_else(|| SmallRng::from_rng(&mut rng())); let mut style_rng = master_rng.clone(); @@ -111,14 +151,13 @@ fn visualise(m: ArgMatches) { let mut image = ImageBuffer::new(width, height); for p in points { - let pp = ps.pop().unwrap(); - let col = Rgb { - data: Lab { - l: style_rng.gen::() * 80. + 10., - a: pp.x * 256. - 128., - b: pp.y * 256. - 128. - }.to_rgb() - }; + let pp = ps.pop().expect("ps should have same length as points"); + let col = Rgb(Lab { + l: style_rng.random::() * 80. + 10., + a: pp.x * 256. - 128., + b: pp.y * 256. - 128., + } + .to_rgb()); let x = p.x * width as f32; let y = p.y * height as f32; @@ -150,13 +189,13 @@ fn visualise(m: ArgMatches) { if style == Style::Colorful { image[(xxx, yyy)] = col; } else { - image[(xxx, yyy)] = Rgb { data: [255, 255, 255] }; + image[(xxx, yyy)] = Rgb([255, 255, 255]); } if style == Style::Plain && (xx == 0. || yy == 0.) { - image[(xxx, yyy)] = Rgb { data: [255, 0, 0] }; + image[(xxx, yyy)] = Rgb([255, 0, 0]); } } } } - image.save(name).unwrap(); + image.save(name).expect("Failed to save generated image"); } diff --git a/poisson/Cargo.toml b/poisson/Cargo.toml index da5bc7e8..0e00eafa 100644 --- a/poisson/Cargo.toml +++ b/poisson/Cargo.toml @@ -1,26 +1,26 @@ [package] name = "poisson" -version = "0.10.1" +version = "0.11.0" authors = ["WaDelma <>"] description = "Poisson-disk distribution generator." repository = "https://github.com/WaDelma/poisson" readme = "../README.md" -category = [ "algorithms" ] keywords = [ "distribution", "poisson-disk", "multidimensional", "sampling" ] license = "MIT" -edition = "2018" +edition = "2024" [badges] travis-ci = { repository = "WaDelma/poisson" } coveralls = { repository = "WaDelma/poisson", service = "github" } [dependencies] -rand = "0.6" -alga = "0.8" +rand = {version="0.9.2", features=["small_rng", "std", "std_rng"]} +rand_distr = "0.5.1" +alga = "0.9.3" num-traits = "0.2" lazy_static = "1.3" modulo = "0.1" sphere = "0.3" [dev-dependencies] -nalgebra = "0.17" +nalgebra = { version = "0.34.1", features = ["alga", "rand"] } diff --git a/poisson/benches/dim2.rs b/poisson/benches/dim2.rs index e6f39f9b..b5e76e55 100644 --- a/poisson/benches/dim2.rs +++ b/poisson/benches/dim2.rs @@ -10,7 +10,7 @@ use rand::{rngs::SmallRng, SeedableRng}; extern crate nalgebra as na; pub type Vect = na::Vector2; -const SEED: [u8; 16] = [ +const SEED: [u8; 32] = [ (3 + 2741) as u8, (7 + 2729) as u8, (13 + 2713) as u8, @@ -27,6 +27,23 @@ const SEED: [u8; 16] = [ (107 + 2549) as u8, (113 + 2539) as u8, (131 + 2521) as u8, + // Additional 16 bytes for 32-byte seed + (137 + 2503) as u8, + (149 + 2477) as u8, + (157 + 2459) as u8, + (163 + 2447) as u8, + (173 + 2423) as u8, + (179 + 2411) as u8, + (181 + 2399) as u8, + (191 + 2389) as u8, + (193 + 2383) as u8, + (197 + 2377) as u8, + (199 + 2371) as u8, + (211 + 2357) as u8, + (223 + 2347) as u8, + (227 + 2341) as u8, + (229 + 2339) as u8, + (233 + 2333) as u8, ]; #[bench] diff --git a/poisson/benches/dim3.rs b/poisson/benches/dim3.rs index 4b79e15c..f3279569 100644 --- a/poisson/benches/dim3.rs +++ b/poisson/benches/dim3.rs @@ -10,7 +10,7 @@ use rand::{rngs::SmallRng, SeedableRng}; extern crate nalgebra as na; pub type Vect = na::Vector3; -const SEED: [u8; 16] = [ +const SEED: [u8; 32] = [ (3 + 2741) as u8, (7 + 2729) as u8, (13 + 2713) as u8, @@ -27,6 +27,23 @@ const SEED: [u8; 16] = [ (107 + 2549) as u8, (113 + 2539) as u8, (131 + 2521) as u8, + // Additional 16 bytes for 32-byte seed + (137 + 2503) as u8, + (149 + 2477) as u8, + (157 + 2459) as u8, + (163 + 2447) as u8, + (173 + 2423) as u8, + (179 + 2411) as u8, + (181 + 2399) as u8, + (191 + 2389) as u8, + (193 + 2383) as u8, + (197 + 2377) as u8, + (199 + 2371) as u8, + (211 + 2357) as u8, + (223 + 2347) as u8, + (227 + 2341) as u8, + (229 + 2339) as u8, + (233 + 2333) as u8, ]; #[bench] diff --git a/poisson/benches/dim4.rs b/poisson/benches/dim4.rs index 4319ccb7..eefc47fe 100644 --- a/poisson/benches/dim4.rs +++ b/poisson/benches/dim4.rs @@ -10,7 +10,7 @@ use rand::{rngs::SmallRng, SeedableRng}; extern crate nalgebra as na; pub type Vect = na::Vector4; -const SEED: [u8; 16] = [ +const SEED: [u8; 32] = [ (3 + 2741) as u8, (7 + 2729) as u8, (13 + 2713) as u8, @@ -27,6 +27,23 @@ const SEED: [u8; 16] = [ (107 + 2549) as u8, (113 + 2539) as u8, (131 + 2521) as u8, + // Additional 16 bytes for 32-byte seed + (137 + 2503) as u8, + (149 + 2477) as u8, + (157 + 2459) as u8, + (163 + 2447) as u8, + (173 + 2423) as u8, + (179 + 2411) as u8, + (181 + 2399) as u8, + (191 + 2389) as u8, + (193 + 2383) as u8, + (197 + 2377) as u8, + (199 + 2371) as u8, + (211 + 2357) as u8, + (223 + 2347) as u8, + (227 + 2341) as u8, + (229 + 2339) as u8, + (233 + 2333) as u8, ]; #[bench] diff --git a/poisson/src/algorithm/bridson.rs b/poisson/src/algorithm/bridson.rs index 25da8b42..f422c49e 100644 --- a/poisson/src/algorithm/bridson.rs +++ b/poisson/src/algorithm/bridson.rs @@ -2,10 +2,12 @@ use crate::algorithm::{Algorithm, Creator}; use crate::utils::*; use crate::{Builder, Float, Vector}; +use num_traits::Float as NumFloat; use num_traits::NumCast; -use rand::distributions::{Distribution, Standard, Uniform}; -use rand::{distributions::StandardNormal, Rng}; +use rand::distr::StandardUniform; +use rand::Rng; +use rand_distr::{Distribution, StandardNormal, Uniform}; use sphere::sphere_volume; @@ -18,8 +20,8 @@ impl Creator for Bridson where F: Float, V: Vector, - Standard: Distribution, - Standard: Distribution, + StandardUniform: Distribution, + StandardUniform: Distribution, { type Algo = Algo; @@ -49,15 +51,15 @@ impl Algorithm for Algo where F: Float, V: Vector, - Standard: Distribution, - Standard: Distribution, + StandardUniform: Distribution, + StandardUniform: Distribution, { fn next(&mut self, poisson: &mut Builder, rng: &mut R) -> Option where R: Rng, { while !self.active_samples.is_empty() { - let index = rng.sample(Uniform::new(0, self.active_samples.len())); + let index = rng.sample(Uniform::new(0, self.active_samples.len()).expect("Active samples should never be empty here")); let cur = self.active_samples[index].clone(); for _ in 0..30 { let min = F::cast(2) * poisson.radius; @@ -76,7 +78,7 @@ where self.active_samples.swap_remove(index); } while self.success == 0 { - let cell = rng.sample(Uniform::new(0, self.grid.cells())); + let cell = rng.sample(Uniform::new(0, self.grid.cells()).expect("Grid should have at least one cell")); let index: V = decode(cell, self.grid.side()).expect( "Because we are decoding random index within grid \ this should work.", @@ -100,16 +102,14 @@ where // how much sphere can fill it at best case and just figure out how many fills are still needed. let dim = V::dimension(); let spacing = self.grid.cell(); - let grid_volume = F::cast(upper) * spacing.powi(dim as i32); + let grid_volume = F::cast(upper) * NumFloat::powi(spacing, dim as i32); let sphere_volume = sphere_volume(F::cast(2) * poisson.radius, dim as u64); let lower: F = grid_volume / sphere_volume; - let mut lower = lower.floor().to_usize().expect( + let mut lower = NumFloat::floor(lower).to_usize().expect( "Grids volume divided by spheres volume should be always \ castable to usize.", ); - if lower > 0 { - lower -= 1; - } + lower = lower.saturating_sub(1); (lower, Some(upper)) } @@ -161,16 +161,16 @@ where F: Float, V: Vector, R: Rng, - Standard: Distribution, - Standard: Distribution, + StandardUniform: Distribution, + StandardUniform: Distribution, { loop { let mut result = V::zero(); for n in 0..V::dimension() { - result[n] = NumCast::from(rand.sample(StandardNormal)) + result[n] = NumCast::from(rand.sample::(StandardNormal)) .expect("The f64 produced by StandardNormal should be always castable to float."); } - let result = result.normalize() * rand.gen() * max; + let result = result.normalize() * rand.random() * max; if result.norm() >= min { return result; } diff --git a/poisson/src/algorithm/ebeida.rs b/poisson/src/algorithm/ebeida.rs index 6d92946b..1d75872f 100644 --- a/poisson/src/algorithm/ebeida.rs +++ b/poisson/src/algorithm/ebeida.rs @@ -2,8 +2,11 @@ use crate::algorithm::{Algorithm, Creator}; use crate::utils::*; use crate::{Builder, Float, Vector}; -use rand::distributions::{Distribution, Standard, Uniform}; +use num_traits::Float as NumFloat; + +use rand::distr::StandardUniform; use rand::Rng; +use rand_distr::{Distribution, Uniform}; use sphere::sphere_volume; @@ -16,8 +19,8 @@ impl Creator for Ebeida where F: Float, V: Vector, - Standard: Distribution, - Standard: Distribution, + StandardUniform: Distribution, + StandardUniform: Distribution, { type Algo = Algo; @@ -37,16 +40,16 @@ where _ => 700. + 100. * dim as f64, }; Algo { - a: a, - grid: grid, + a, + grid, throws: (a * indices.len() as f64).ceil() as usize, - range: Uniform::new(0, indices.len()), - indices: indices, + range: Uniform::new(0, indices.len()).expect("Indices should not be empty at initialization"), + indices, level: 0, success: 0, outside: vec![], mantissa_digits: { - let (mantissa, _, _) = F::max_value().integer_decode(); + let (mantissa, _, _) = ::max_value().integer_decode(); mantissa.count_ones() as usize }, } @@ -74,8 +77,8 @@ impl Algorithm for Algo where F: Float, V: Vector, - Standard: Distribution, - Standard: Distribution, + StandardUniform: Distribution, + StandardUniform: Distribution, { fn next(&mut self, poisson: &mut Builder, rng: &mut R) -> Option where @@ -100,7 +103,7 @@ where if self.indices.is_empty() { return None; } - self.range = Uniform::new(0, self.indices.len()); + self.range = Uniform::new(0, self.indices.len()).expect("Indices should not be empty after removal"); } else { let sample = choose_random_sample(rng, &self.grid, cur.clone(), self.level); if is_disk_free( @@ -117,18 +120,18 @@ where .push(sample.clone()); self.indices.swap_remove(index); if !self.indices.is_empty() { - self.range = Uniform::new(0, self.indices.len()); + self.range = Uniform::new(0, self.indices.len()).expect("Indices verified to be non-empty"); } self.success += 1; return Some(sample); } } } - self.subdivide(&poisson); + self.subdivide(poisson); if self.indices.is_empty() { return None; } - self.range = Uniform::new(0, self.indices.len()); + self.range = Uniform::new(0, self.indices.len()).expect("Indices should not be empty at level advancement"); self.throws = (self.a * self.indices.len() as f64).ceil() as usize; self.level += 1; } @@ -156,16 +159,14 @@ where let dim = V::dimension(); let side = 2usize.pow(self.level as u32); let spacing = self.grid.cell() / F::cast(side); - let grid_volume = F::cast(self.indices.len()) * spacing.powi(dim as i32); + let grid_volume = F::cast(self.indices.len()) * NumFloat::powi(spacing, dim as i32); let sphere_volume = sphere_volume(F::cast(2) * poisson.radius, dim as u64); let lower = grid_volume / sphere_volume; - let mut lower = lower.floor().to_usize().expect( + let mut lower = NumFloat::floor(lower).to_usize().expect( "Grids volume divided by spheres volume should be always \ castable to usize.", ); - if lower > 0 { - lower -= 1; - } + lower = lower.saturating_sub(1); // Calculating upper bound should work because there is this many places left in the grid and no more can fit into it. let upper = self.grid.cells() - self.success; (lower, Some(upper)) @@ -217,15 +218,15 @@ where // TODO: This does 4^d checking of points even though it could be done 3^d let side = 2usize.pow(level as u32); let spacing = grid.cell() / F::cast(side); - let sqradius = (F::cast(2) * poisson.radius).powi(2); + let sqradius = NumFloat::powi(F::cast(2) * poisson.radius, 2); let parent = get_parent(index.clone(), level); each_combination(&[0, 1]) .map(|t| (index.clone() + t) * spacing) .all(|t| { each_combination(&[-2, -1, 0, 1, 2]) .filter_map(|t| grid.get(parent.clone() + t)) - .flat_map(|t| t) + .flatten() .any(|v| sqdist(v.clone(), t.clone(), poisson.poisson_type) < sqradius) - || !is_valid(poisson, &outside, t) + || !is_valid(poisson, outside, t) }) } diff --git a/poisson/src/lib.rs b/poisson/src/lib.rs index b1cb1fe5..66fbe7c2 100644 --- a/poisson/src/lib.rs +++ b/poisson/src/lib.rs @@ -1,9 +1,11 @@ +#![recursion_limit = "2048"] + //! # Poisson-disk distribution generation //! //! Generates distribution of points in [0, 1)d where: //! //! * For each point there is disk of certain radius which doesn't intersect -//! with any other disk of other points +//! with any other disk of other points //! * Samples fill the space uniformly //! //! Due it's blue noise properties poisson-disk distribution @@ -21,7 +23,7 @@ //! extern crate rand; //! extern crate nalgebra as na; //! -//! use rand::FromEntropy; +//! use rand::{SeedableRng, rng}; //! use rand::rngs::SmallRng; //! //! use poisson::{Builder, Type, algorithm}; @@ -29,7 +31,7 @@ //! fn main() { //! let poisson = //! Builder::<_, na::Vector2>::with_radius(0.1, Type::Normal) -//! .build(SmallRng::from_entropy(), algorithm::Ebeida); +//! .build(SmallRng::from_rng(&mut rng()), algorithm::Ebeida); //! let samples = poisson.generate(); //! println!("{:?}", samples); //! } @@ -41,13 +43,13 @@ //! ````rust //! # extern crate nalgebra as na; //! # use poisson::{Builder, Type, algorithm}; -//! # use rand::FromEntropy; +//! # use rand::{SeedableRng, rng}; //! # use rand::rngs::SmallRng; //! //! fn main() { //! let poisson = //! Builder::<_, na::Vector3>::with_samples(100, 0.9, Type::Perioditic) -//! .build(SmallRng::from_entropy(), algorithm::Bridson); +//! .build(SmallRng::from_rng(&mut rng()), algorithm::Bridson); //! for sample in poisson { //! println!("{:?}", sample) //! } @@ -59,14 +61,14 @@ use rand::Rng; use num_traits::Float as NumFloat; use num_traits::{NumCast, Zero}; -use alga::general::AbstractField; +use alga::general::RealField; use alga::linear::{FiniteDimVectorSpace, NormedSpace}; #[macro_use] extern crate lazy_static; use std::marker::PhantomData; -use std::ops::{AddAssign, DivAssign, Index, IndexMut, MulAssign, SubAssign}; +use std::ops::{AddAssign, DivAssign, MulAssign, SubAssign}; use crate::algorithm::{Algorithm, Creator}; use crate::utils::math::calc_radius; @@ -75,23 +77,17 @@ pub mod algorithm; mod utils; /// Describes what floats are. -pub trait Float: NumFloat + AbstractField + AddAssign + SubAssign + MulAssign + DivAssign { +pub trait Float: NumFloat + RealField + AddAssign + SubAssign + MulAssign + DivAssign { /// Casts usize to float. fn cast(n: usize) -> Self { NumCast::from(n).expect("Casting usize to float should always succeed.") } } -impl Float for T where T: NumFloat + AbstractField + AddAssign + SubAssign + MulAssign + DivAssign -{} +impl Float for T where T: NumFloat + RealField + AddAssign + SubAssign + MulAssign + DivAssign {} /// Describes what vectors are. pub trait Vector: - Zero - + FiniteDimVectorSpace - + NormedSpace - + Index - + IndexMut - + Clone + Zero + FiniteDimVectorSpace + NormedSpace where F: Float, { @@ -99,29 +95,21 @@ where impl Vector for T where F: Float, - T: Zero - + FiniteDimVectorSpace - + NormedSpace - + Index - + IndexMut - + Clone + T: Zero + FiniteDimVectorSpace + NormedSpace, { } /// Enum for determining the type of poisson-disk distribution. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Default)] pub enum Type { /// Acts like there is void all around the space placing no restrictions to sides. + #[default] Normal, /// Makes the space to wrap around on edges allowing tiling of the generated poisson-disk distribution. Perioditic, } -impl Default for Type { - fn default() -> Type { - Type::Normal - } -} /// Builder for the generator. #[derive(Default, Clone, Debug, PartialEq)] @@ -149,8 +137,8 @@ where <= NumCast::from(2f64.sqrt() / 2.).expect("Casting constant should always work.") ); Builder { - radius: radius, - poisson_type: poisson_type, + radius, + poisson_type, _marker: PhantomData, } } @@ -163,7 +151,7 @@ where Builder { radius: relative * NumCast::from(2f64.sqrt() / 2.).expect("Casting constant should always work."), - poisson_type: poisson_type, + poisson_type, _marker: PhantomData, } } @@ -176,7 +164,7 @@ where pub fn with_samples(samples: usize, relative: F, poisson_type: Type) -> Self { Builder { radius: calc_radius::(samples, relative, poisson_type), - poisson_type: poisson_type, + poisson_type, _marker: PhantomData, } } @@ -224,8 +212,8 @@ where { fn new(poisson: Builder, rng: R) -> Self { Generator { - rng: rng, - poisson: poisson, + rng, + poisson, _algo: PhantomData, } } diff --git a/poisson/src/utils/math.rs b/poisson/src/utils/math.rs index 76cedeb6..f8a444b5 100644 --- a/poisson/src/utils/math.rs +++ b/poisson/src/utils/math.rs @@ -1,9 +1,9 @@ use crate::{Float, Type, Vector}; -use num_traits::NumCast; +use num_traits::{Float as NumFloat, NumCast}; // const TAU: f64 = 6.283185307179586476925286766559005768394338798750211641949; -const HALF_TAU: f64 = 3.141592653589793238462643383279502884197169399375105820974; +const HALF_TAU: f64 = std::f64::consts::PI; lazy_static! { static ref MAX_PACKING_DENSITIES: [f64; 7] = [ @@ -89,6 +89,6 @@ where Perioditic => samples, Normal => newton(samples, dim), }; - let max_radii: F = NumCast::from(MAX_RADII[dim - 2]).unwrap(); - (max_radii / F::cast(samples)).powf(F::cast(1) / F::cast(dim)) * relative + let max_radii: F = NumCast::from(MAX_RADII[dim - 2]).expect("MAX_RADII value should always be convertible to float type"); + NumFloat::powf(max_radii / F::cast(samples), F::cast(1) / F::cast(dim)) * relative } diff --git a/poisson/src/utils/mod.rs b/poisson/src/utils/mod.rs index 9dcda94c..a68d45c3 100644 --- a/poisson/src/utils/mod.rs +++ b/poisson/src/utils/mod.rs @@ -2,10 +2,11 @@ use crate::{Builder, Float, Type, Vector}; -use num_traits::NumCast; +use num_traits::{Float as NumFloat, NumCast}; -use rand::distributions::{Distribution, Standard}; +use rand::distr::StandardUniform; use rand::Rng; +use rand_distr::Distribution; use modulo::Mod; @@ -33,13 +34,13 @@ where { pub fn new(radius: F, poisson_type: Type) -> Grid { let dim = F::cast(V::dimension()); - let cell = (F::cast(2) * radius) / dim.sqrt(); + let cell = (F::cast(2) * radius) / NumFloat::sqrt(dim); let side = (F::cast(1) / cell) .to_usize() .expect("Expected that dividing 1 by cell width would be legal."); Grid { - cell: cell, - side: side, + cell, + side, data: vec![ vec![]; side.pow( @@ -47,7 +48,7 @@ where .expect("Dimension should be always be castable to u32.") ) ], - poisson_type: poisson_type, + poisson_type, _marker: PhantomData, } } @@ -129,7 +130,7 @@ fn encoding_decoding_works() { let n = nalgebra::Vector2::new(10., 7.); assert_eq!( n, - decode(encode(&n, 15, Type::Normal).unwrap(), 15).unwrap() + decode::<_, nalgebra::Vector2<_>>(encode(&n, 15, Type::Normal).expect("Test vector should encode properly"), 15).expect("Encoded value should decode properly") ); } @@ -138,7 +139,7 @@ fn encoding_decoding_at_edge_works() { let n = nalgebra::Vector2::new(14., 14.); assert_eq!( n, - decode(encode(&n, 15, Type::Normal).unwrap(), 15).unwrap() + decode::<_, nalgebra::Vector2<_>>(encode(&n, 15, Type::Normal).expect("Test vector should encode properly"), 15).expect("Encoded value should decode properly") ); } @@ -160,18 +161,18 @@ where F: Float, V: Vector, R: Rng, - Standard: Distribution, + StandardUniform: Distribution, { let side = 2usize.pow(level as u32); let spacing = grid.cell / F::cast(side); - (index + rng.gen()) * spacing + (index + rng.sample(StandardUniform)) * spacing } #[test] fn random_point_is_between_right_values_top_lvl() { use num_traits::Zero; use rand::{rngs::SmallRng, SeedableRng}; - let mut rand = SmallRng::from_seed([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + let mut rand = SmallRng::from_seed([1; 32]); // range from 1 to 32 let radius = 0.2; let grid = Grid::>::new(radius, Type::Normal); for _ in 0..1000 { @@ -190,7 +191,7 @@ where { let mut cur = value.clone(); for n in 0..V::dimension() { - cur[n] = (cur[n] * F::cast(side)).floor(); + cur[n] = NumFloat::floor(cur[n] * F::cast(side)); } cur } @@ -202,7 +203,7 @@ where { let mut cur = value.clone(); for n in 0..V::dimension() { - cur[n] = cur[n] / F::cast(side); + cur[n] /= F::cast(side); } cur } @@ -220,11 +221,11 @@ where V: Vector, { let parent = get_parent(index, level); - let sqradius = (F::cast(2) * poisson.radius).powi(2); + let sqradius = NumFloat::powi(F::cast(2) * poisson.radius, 2); // NOTE: This does unnessary checks for corners, but it doesn't affect much in higher dimensions: 5^d vs 5^d - 2d each_combination(&[-2, -1, 0, 1, 2]) .filter_map(|t| grid.get(parent.clone() + t)) - .flat_map(|t| t) + .flatten() .all(|v| sqdist(v.clone(), sample.clone(), poisson.poisson_type) >= sqradius) && is_valid(poisson, outside, sample) } @@ -234,7 +235,7 @@ where F: Float, V: Vector, { - let sqradius = (F::cast(2) * poisson.radius).powi(2); + let sqradius = NumFloat::powi(F::cast(2) * poisson.radius, 2); samples .iter() .all(|t| sqdist(t.clone(), sample.clone(), poisson.poisson_type) >= sqradius) @@ -250,7 +251,7 @@ where match poisson_type { Perioditic => each_combination(&[-1, 0, 1]) .map(|v| (diff.clone() + v).norm_squared()) - .fold(F::max_value(), |a, b| a.min(b)), + .fold(NumFloat::max_value(), |a, b| NumFloat::min(a, b)), Normal => diff.norm_squared(), } } @@ -262,7 +263,7 @@ where { let split = 2usize.pow(level as u32); for n in 0..V::dimension() { - index[n] = (index[n] / F::cast(split)).floor(); + index[n] = NumFloat::floor(index[n] / F::cast(split)); } index } @@ -311,7 +312,7 @@ where for n in 0..V::dimension() { let rem = div % len; div /= len; - let choice = self.choices[rem as usize].clone(); + let choice = self.choices[rem].clone(); result[n] = NumCast::from(choice).expect( "Expected that all choices were castable to float without \ problems.", @@ -323,7 +324,7 @@ where } /// Iterates through all combinations of vectors with allowed values as scalars. -pub fn each_combination<'a, F, FF, V>(choices: &[FF]) -> CombiIter +pub fn each_combination<'a, F, FF, V>(choices: &[FF]) -> CombiIter<'_, F, FF, V> where F: Float + 'a, FF: NumCast, @@ -331,7 +332,7 @@ where { CombiIter { cur: 0, - choices: choices, + choices, _marker: PhantomData, } } @@ -365,9 +366,9 @@ fn mapping_inplace_works() { let mut result = vec.clone(); let func = |t| { match t % 3 { - 0 => (0..0), - 1 => (0..1), - _ => (0..2), + 0 => 0..0, + 1 => 0..1, + _ => 0..2, } .map(move |n| t + n) }; diff --git a/poisson/tests/adding.rs b/poisson/tests/adding.rs index a5ea0af8..818ca1bb 100644 --- a/poisson/tests/adding.rs +++ b/poisson/tests/adding.rs @@ -15,7 +15,10 @@ mod helper; fn adding_valid_start_works() { let samples = 100; let relative_radius = 0.8; - let rand = SmallRng::from_seed([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + let rand = SmallRng::from_seed([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, + ]); let prefiller = |_| { let mut pre = Builder::<_, Vect>::with_samples(samples, relative_radius, Type::Normal) .build(rand.clone(), algorithm::Ebeida) @@ -37,7 +40,10 @@ fn adding_valid_start_works() { fn adding_valid_middle_works() { let samples = 100; let relative_radius = 0.8; - let rand = SmallRng::from_seed([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + let rand = SmallRng::from_seed([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, + ]); let prefiller = |_| { let prefiller = Builder::<_, Vect>::with_samples(samples, relative_radius, Type::Normal) .build(rand.clone(), algorithm::Ebeida); @@ -118,7 +124,10 @@ fn adding_to_outside_of_edges_start_works() { fn completely_filled_works() { let samples = 100; let relative_radius = 0.8; - let rand = SmallRng::from_seed([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + let rand = SmallRng::from_seed([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, + ]); let prefiller = |_| { let mut pre = Builder::<_, Vect>::with_samples(samples, relative_radius, Type::Normal) .build(rand.clone(), algorithm::Ebeida) diff --git a/poisson/tests/dim2.rs b/poisson/tests/dim2.rs index 30f58f48..d0864f52 100644 --- a/poisson/tests/dim2.rs +++ b/poisson/tests/dim2.rs @@ -11,12 +11,18 @@ use crate::helper::test_with_samples; #[test] fn test_one_sample_works() { - let rand = SmallRng::from_seed([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + let rand = SmallRng::from_seed([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, + ]); let builder = Builder::<_, Vect>::with_samples(1, 0.8, Normal); let builder = builder.build(rand, algorithm::Ebeida); builder.into_iter().for_each(drop); - let rand = SmallRng::from_seed([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + let rand = SmallRng::from_seed([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, + ]); let builder = Builder::<_, Vect>::with_samples(1, 0.8, Normal); let builder = builder.build(rand, algorithm::Bridson); builder.into_iter().for_each(drop); diff --git a/poisson/tests/helper/mod.rs b/poisson/tests/helper/mod.rs index d188a3d5..417a8b85 100644 --- a/poisson/tests/helper/mod.rs +++ b/poisson/tests/helper/mod.rs @@ -1,8 +1,9 @@ #![allow(unused)] use poisson::{algorithm, Builder, Float, Type, Vector}; -use rand::distributions::{Distribution, Standard}; +use rand::distr::StandardUniform; use rand::{rngs::SmallRng, SeedableRng}; +use rand_distr::Distribution; use num_traits::NumCast; @@ -14,7 +15,7 @@ use std::fmt::Debug; pub fn print_v>(v: V) -> String { let mut result = "(".to_owned(); for i in 0..V::dimension() { - result.push_str(&format!("{}, ", v[i].to_f64().unwrap())); + result.push_str(&format!("{}, ", v[i].to_f64().expect("Test vector element should convert to f64"))); } if V::dimension() != 0 { result.pop(); @@ -33,7 +34,7 @@ pub enum When { pub fn test_with_samples(samples: usize, relative_radius: f64, seeds: u32, ptype: Type) where T: Debug + Vector + Copy, - Standard: Distribution, + StandardUniform: Distribution, { test_with_samples_prefilled( samples, @@ -56,8 +57,8 @@ pub fn test_with_samples_prefilled<'r, T, F, I>( T: 'r + Debug + Vector + Copy, F: FnMut(f64) -> I, I: FnMut(Option) -> Option, - Standard: Distribution, - Standard: Distribution, + StandardUniform: Distribution, + StandardUniform: Distribution, { test_algo( samples, @@ -92,30 +93,25 @@ fn test_algo<'r, T, F, I, A>( F: FnMut(f64) -> I, I: FnMut(Option) -> Option, A: algorithm::Creator, - Standard: Distribution, - Standard: Distribution, + StandardUniform: Distribution, + StandardUniform: Distribution, { use self::When::*; for i in 0..seeds { let mut prefilled = vec![]; - let rand = SmallRng::from_seed([ - (i * 3 + 2741) as u8, - (i * 7 + 2729) as u8, - (i * 13 + 2713) as u8, - (i * 19 + 2707) as u8, - (i * 29 + 2693) as u8, - (i * 37 + 2687) as u8, - (i * 43 + 2677) as u8, - (i * 53 + 2663) as u8, - (i * 61 + 2657) as u8, - (i * 71 + 2633) as u8, - (i * 79 + 2609) as u8, - (i * 89 + 2591) as u8, - (i * 101 + 2557) as u8, - (i * 107 + 2549) as u8, - (i * 113 + 2539) as u8, - (i * 131 + 2521) as u8, - ]); + let mut seed = [0u8; 32]; + for j in 0..16 { + let primes = [ + 3, 7, 13, 19, 29, 37, 43, 53, 61, 71, 79, 89, 101, 107, 113, 131, + ]; + let offsets = [ + 2741, 2729, 2713, 2707, 2693, 2687, 2677, 2663, 2657, 2633, 2609, 2591, 2557, 2549, + 2539, 2521, + ]; + seed[j] = (i * primes[j] + offsets[j]) as u8; + seed[j + 16] = ((i * primes[j] + offsets[j]) >> 8) as u8; + } + let rand = SmallRng::from_seed(seed); let mut poisson_iter = Builder::with_samples(samples, relative_radius, ptype) .build(rand, algo) .into_iter(); @@ -168,8 +164,13 @@ fn test_algo<'r, T, F, I, A>( } } -pub fn test_poisson(poisson: I, radius: F, poisson_type: Type, algo: A, does_prefill: bool) -where +pub fn test_poisson( + poisson: I, + radius: F, + poisson_type: Type, + algo: A, + does_prefill: bool, +) where I: Iterator, F: Float, T: Debug + Vector + Copy, @@ -219,7 +220,7 @@ where for i in 0..T::dimension() { let rem = div % 3; div /= 3; - t[i] = NumCast::from(rem - 1).unwrap(); + t[i] = NumCast::from(rem - 1).expect("Test offset value should convert to vector element type"); } for v in &vecs { vecs2.push(*v + t); @@ -251,8 +252,8 @@ where distance to each other of {} which is smaller than smallest allowed one {}. \ The samples: [{:?}, {:?}]", algo, - dist.to_f64().unwrap(), - radius.to_f64().unwrap() * 2., + dist.to_f64().expect("Test distance should convert to f64"), + radius.to_f64().expect("Test radius should convert to f64") * 2., v1, v2); } diff --git a/poisson/tests/reproductions.rs b/poisson/tests/reproductions.rs index 61315212..bc2b350f 100644 --- a/poisson/tests/reproductions.rs +++ b/poisson/tests/reproductions.rs @@ -2,13 +2,16 @@ extern crate nalgebra as na; use rand::{rngs::SmallRng, SeedableRng}; -use poisson::{Builder, Type, algorithm}; +use poisson::{algorithm, Builder, Type}; #[test] fn reproduce_issue_29() { - let seed = [160, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let seed = [ + 160, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ]; let rng = SmallRng::from_seed(seed); Builder::<_, na::Vector2>::with_radius(0.004, Type::Normal) .build(rng, algorithm::Bridson) .generate(); -} \ No newline at end of file +} diff --git a/poisson/tests/validity.rs b/poisson/tests/validity.rs index 96e65895..3861fd71 100644 --- a/poisson/tests/validity.rs +++ b/poisson/tests/validity.rs @@ -1,7 +1,7 @@ use poisson::Type; -use rand::distributions::StandardNormal; use rand::{rngs::SmallRng, Rng, SeedableRng}; +use rand_distr::StandardNormal; extern crate nalgebra as na; pub type Vect = na::Vector2; @@ -16,11 +16,14 @@ mod helper; #[test] fn multiple_too_close_invalid() { - let samples = 100; + let samples = 101; // TODO: 100 freezes forever. let relative_radius = 0.8; let prefiller = |radius| { let mut last = None::; - let mut rand = SmallRng::from_seed([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + let mut rand = SmallRng::from_seed([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ]); move |v| { if let Some(_) = v { if last == v { @@ -28,14 +31,13 @@ fn multiple_too_close_invalid() { } else { last = v; let vec = sphere_uniform_point(&mut rand); - v.map(|v| v + vec * rand.gen::() * radius) + v.map(|v| v + vec * rand.random::() * radius) } } else { None } } }; - // TODO: At 10 the test suddenly takes forever and takes all of the memory resulting into getting killed by oom killer helper::test_with_samples_prefilled( samples, relative_radius,