diff --git a/src/algorithm/bridson.rs b/src/algorithm/bridson.rs index 0d16a815..2dd3e108 100644 --- a/src/algorithm/bridson.rs +++ b/src/algorithm/bridson.rs @@ -1,4 +1,4 @@ -use {Builder, Vector, Float}; +use {PoissonConfiguration, Vector, Float}; use algorithm::{Creator, Algorithm}; use utils::*; @@ -22,7 +22,7 @@ impl Creator for Bridson { type Algo = Algo; - fn create(poisson: &Builder) -> Self::Algo { + fn create(poisson: &PoissonConfiguration) -> Self::Algo { Algo { grid: Grid::new(poisson.radius, poisson.poisson_type), active_samples: vec![], @@ -46,7 +46,7 @@ impl Algorithm for Algo where F: Float, V: Vector { - fn next(&mut self, poisson: &mut Builder, rng: &mut R) -> Option + fn next(&mut self, poisson: &mut PoissonConfiguration, rng: &mut R) -> Option where R: Rng { while !self.active_samples.is_empty() { @@ -78,7 +78,7 @@ impl Algorithm for Algo None } - fn size_hint(&self, poisson: &Builder) -> (usize, Option) { + fn size_hint(&self, poisson: &PoissonConfiguration) -> (usize, Option) { // Calculating upper bound should work because there is this many places left in the grid and no more can fit into it. let upper = if self.grid.cells() > self.success { self.grid.cells() - self.success @@ -112,7 +112,7 @@ impl Algorithm for Algo } } - fn stays_legal(&self, poisson: &Builder, sample: V) -> bool { + fn stays_legal(&self, poisson: &PoissonConfiguration, sample: V) -> bool { let index = sample_to_index(&sample, self.grid.side()); is_disk_free(&self.grid, poisson, index, 0, sample.clone(), &self.outside) } @@ -122,7 +122,7 @@ impl Algo where F: Float, V: Vector { - fn insert_if_valid(&mut self, poisson: &mut Builder, index: V, sample: V) -> bool { + fn insert_if_valid(&mut self, poisson: &mut PoissonConfiguration, index: V, sample: V) -> bool { if is_disk_free(&self.grid, poisson, index.clone(), diff --git a/src/algorithm/ebeida.rs b/src/algorithm/ebeida.rs index 5718241a..672a3725 100644 --- a/src/algorithm/ebeida.rs +++ b/src/algorithm/ebeida.rs @@ -1,4 +1,4 @@ -use {Builder, Vector, Float}; +use {PoissonConfiguration, Vector, Float}; use algorithm::{Creator, Algorithm}; use utils::*; @@ -19,7 +19,7 @@ impl Creator for Ebeida { type Algo = Algo; - fn create(poisson: &Builder) -> Self::Algo { + fn create(poisson: &PoissonConfiguration) -> Self::Algo { let dim = V::dim(None); let grid = Grid::new(poisson.radius, poisson.poisson_type); let mut indices = Vec::with_capacity(grid.cells() * dim); @@ -70,7 +70,7 @@ impl Algorithm for Algo where F: Float, V: Vector { - fn next(&mut self, poisson: &mut Builder, rng: &mut R) -> Option + fn next(&mut self, poisson: &mut PoissonConfiguration, rng: &mut R) -> Option where R: Rng { if self.indices.is_empty() { @@ -136,7 +136,7 @@ impl Algorithm for Algo } } - fn size_hint(&self, poisson: &Builder) -> (usize, Option) { + fn size_hint(&self, poisson: &PoissonConfiguration) -> (usize, Option) { // Calculating lower bound should work because we calculate how much volume is left to be filled at worst case and // how much sphere can fill it at best case and just figure out how many fills are still needed. let dim = V::dim(None); @@ -167,7 +167,7 @@ impl Algorithm for Algo } } - fn stays_legal(&self, poisson: &Builder, sample: V) -> bool { + fn stays_legal(&self, poisson: &PoissonConfiguration, sample: V) -> bool { let index = sample_to_index(&sample, self.grid.side()); is_disk_free(&self.grid, poisson, index, 0, sample.clone(), &self.outside) } @@ -177,7 +177,7 @@ impl Algo where F: Float, V: Vector { - fn subdivide(&mut self, poisson: &Builder) { + fn subdivide(&mut self, poisson: &PoissonConfiguration) { let choices = &[0, 1]; let (grid, outside, level) = (&self.grid, &self.outside, self.level); self.indices.flat_map_inplace(|i| { @@ -189,7 +189,7 @@ impl Algo } fn covered(grid: &Grid, - poisson: &Builder, + poisson: &PoissonConfiguration, outside: &[V], index: V, level: usize) diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs index b5fda5c4..20ba2c9d 100644 --- a/src/algorithm/mod.rs +++ b/src/algorithm/mod.rs @@ -1,6 +1,6 @@ //! Module that contains traits that describe poisson-disk distribution generating algorithms. -use {Builder, Vector, Float}; +use {PoissonConfiguration, Vector, Float}; use rand::Rng; @@ -20,7 +20,7 @@ pub trait Creator: Copy + Debug type Algo: Algorithm; /// Creates new algorithm. - fn create(&Builder) -> Self::Algo; + fn create(&PoissonConfiguration) -> Self::Algo; } /// Trait that describes what poisson-disk distribution generating algorithm needs. @@ -28,15 +28,15 @@ pub trait Algorithm where F: Float, V: Vector, { - /// Advances algorithm based on Builder and Rng. - fn next(&mut self, &mut Builder, &mut R) -> Option where R: Rng; + /// Advances algorithm based on PoissonConfiguration and Rng. + fn next(&mut self, &mut PoissonConfiguration, &mut R) -> Option where R: Rng; - /// Return lower and upper bound of samples remaining for algorithm to generate based on Builder. - fn size_hint(&self, &Builder) -> (usize, Option); + /// Return lower and upper bound of samples remaining for algorithm to generate based on PoissonConfiguration. + fn size_hint(&self, &PoissonConfiguration) -> (usize, Option); /// Restricts the algorithm with arbitary sample. fn restrict(&mut self, V); - /// Checks if sample is valid based on Builder. - fn stays_legal(&self, &Builder, V) -> bool; + /// Checks if sample is valid based on PoissonConfiguration. + fn stays_legal(&self, &PoissonConfiguration, V) -> bool; } diff --git a/src/lib.rs b/src/lib.rs index e8502d53..64b814d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,8 +19,8 @@ //! //! fn main() { //! let poisson = -//! Builder::<_, Vec2>::with_radius(0.1, Type::Normal) -//! .build(rand::weak_rng(), algorithm::Ebeida); +//! Builder::<_, Vec2>::new().with_poisson_type(Type::Normal).with_radius(0.1) +//! .build(rand::weak_rng(), algorithm::Ebeida).unwrap(); //! let samples = poisson.generate(); //! println!("{:?}", samples); //! } @@ -92,9 +92,21 @@ impl Default for Type { } } -/// Builder for the generator. -#[derive(Default, Clone, Debug, PartialEq)] -pub struct Builder +/// This is the error type for the `Builder::build` function. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum ConfigurationError { + RadiusInvalid, + RadiusUnspecified, + RadiusMultiplyDefined, + PoissonTypeUnspecified, + PoissonTypeMultiplyDefined, + SampleCountInvalid, + SampleApproximationUnsupported, + PoissonTypeUnknownAtRadiusCalculation, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct PoissonConfiguration where F: Float, V: Vector { @@ -103,64 +115,128 @@ pub struct Builder _marker: PhantomData, } +/// Builder for the Generator. +#[derive(Default, Clone, Debug, PartialEq)] +pub struct Builder + where F: Float, + V: Vector +{ + radius: Option, + poisson_type: Option, + error: Option, + _marker: PhantomData, +} + impl Builder where F: Float, V: Vector { - /// New Builder with type of distribution and radius specified. - /// The radius should be ]0, √2 / 2] - pub fn with_radius(radius: F, poisson_type: Type) -> Self { - assert!(F::cast(0) < radius); - assert!(radius <= - NumCast::from(2f64.sqrt() / 2.).expect("Casting constant should always work.")); + /// Creates a new Builder with all options unset + pub fn new() -> Self { Builder { - radius: radius, - poisson_type: poisson_type, + radius: None, + poisson_type: None, + error: None, _marker: PhantomData, } } - /// New Builder with type of distribution and relative radius specified. - /// The relative radius should be ]0, 1] - pub fn with_relative_radius(relative: F, poisson_type: Type) -> Self { - assert!(relative >= F::cast(0)); - assert!(relative <= F::cast(1)); - Builder { - radius: relative * - NumCast::from(2f64.sqrt() / 2.).expect("Casting constant should always work."), - poisson_type: poisson_type, - _marker: PhantomData, + /// Confirms that the radius has not been set before. + /// Sets the error accordingly otherwise. + fn set_radius(&mut self, radius: F) { + if self.radius.is_some() { + self.error = Some(ConfigurationError::RadiusMultiplyDefined); + } else { + self.radius = Some(radius); + } + } + + /// Confirms that the Poisson type has not been set before. + /// Sets the error accordingly otherwise. + fn set_poisson_type(&mut self, pt: Type) { + if self.poisson_type.is_some() { + self.error = Some(ConfigurationError::PoissonTypeMultiplyDefined); + } else { + self.poisson_type = Some(pt); + } + } + + /// Configures the Builder to a specific radius. + /// The radius must be ]0, √2 / 2] + pub fn with_radius(mut self, radius: F) -> Self { + if F::cast(0) >= radius || radius > NumCast::from(2f64.sqrt() / 2.).expect("Casting constant should always work.") { + self.error = Some(ConfigurationError::RadiusInvalid); } + self.set_radius(radius); + self } - /// New Builder with type of distribution, approximate amount of samples and relative radius specified. + /// Configures the Builder to use the specified relative radius. + /// The relative radius must be ]0, 1] + pub fn with_relative_radius(mut self, relative: F) -> Self { + if relative < F::cast(0) || relative > F::cast(1) { + self.error = Some(ConfigurationError::RadiusInvalid); + } + self.set_radius(relative * NumCast::from(2f64.sqrt() / 2.).expect("Casting constant should always work.")); + self + } + + /// Configure the Builder with an approximate amount of samples and a relative radius. /// The amount of samples should be larger than 0. /// The relative radius should be [0, 1]. /// For non-perioditic this is supported only for 2, 3 and 4 dimensional generation. - pub fn with_samples(samples: usize, relative: F, poisson_type: Type) -> Self { - Builder { - radius: calc_radius::(samples, relative, poisson_type), - poisson_type: poisson_type, - _marker: PhantomData, + /// Call this function only after setting the Poisson type. + pub fn with_samples(mut self, samples: usize, relative: F) -> Self { + match self.poisson_type { + Some(poisson_type) => { + match calc_radius::(samples, relative, poisson_type) { + Ok(radius) => self.set_radius(radius), + Err(err) => self.error = Some(err), + } + } + None => { + self.error = Some(ConfigurationError::PoissonTypeUnknownAtRadiusCalculation); + } } + self + } + + /// Configure the Builder to use the specified Poisson type. + pub fn with_poisson_type(mut self, poisson_type: Type) -> Self { + self.set_poisson_type(poisson_type); + self } /// Returns the radius of the generator. - pub fn radius(&self) -> F { + pub fn radius(&self) -> Option { self.radius } /// Returns the type of the generator. - pub fn poisson_type(&self) -> Type { + pub fn poisson_type(&self) -> Option { self.poisson_type } + /// Returns the error about the configuration, if any. + pub fn error(&self) -> Result<(),ConfigurationError> { + match self.error { + Some(err) => Err(err), + None => Ok(()), + } + } + /// Builds generator with random number generator and algorithm specified. - pub fn build(self, rng: R, _algo: A) -> Generator + pub fn build(self, rng: R, _algo: A) -> Result,ConfigurationError> where R: Rng, A: Creator { - Generator::new(self, rng) + try!(self.error()); + let config = PoissonConfiguration { + radius: try!(self.radius.ok_or(ConfigurationError::RadiusUnspecified)), + poisson_type: try!(self.poisson_type.ok_or(ConfigurationError::PoissonTypeUnspecified)), + _marker: PhantomData, + }; + Ok(Generator::new(config, rng)) } } @@ -172,7 +248,7 @@ pub struct Generator R: Rng, A: Creator { - poisson: Builder, + poisson: PoissonConfiguration, rng: R, _algo: PhantomData, } @@ -183,7 +259,7 @@ impl Generator R: Rng, A: Creator { - fn new(poisson: Builder, rng: R) -> Self { + fn new(poisson: PoissonConfiguration, rng: R) -> Self { Generator { rng: rng, poisson: poisson, @@ -248,7 +324,7 @@ pub struct PoissonIter R: Rng, A: Algorithm { - poisson: Builder, + poisson: PoissonConfiguration, rng: R, algo: A, } diff --git a/src/utils/math.rs b/src/utils/math.rs index 824e60ae..aeecfc23 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -1,4 +1,4 @@ -use {Type, Vector, Float}; +use {Type, Vector, Float, ConfigurationError}; use num::NumCast; @@ -74,20 +74,25 @@ fn newton(samples: usize, dim: usize) -> usize { /// The amount of samples should be larger than 0. /// The relative radius should be [0, 1]. /// For non-perioditic this is supported only for 2, 3 and 4 dimensional generation. -pub fn calc_radius(samples: usize, relative: F, poisson_type: Type) -> F +pub fn calc_radius(samples: usize, relative: F, poisson_type: Type) -> Result where F: Float, V: Vector { use Type::*; - assert!(Type::Perioditic == poisson_type || V::dim(None) < 5); - assert!(samples > 0); - assert!(relative >= F::cast(0)); - assert!(relative <= F::cast(1)); + if !(Type::Perioditic == poisson_type || V::dim(None) < 5) { + return Err(ConfigurationError::SampleApproximationUnsupported); + } + if samples == 0 { + return Err(ConfigurationError::SampleCountInvalid); + } + if relative < F::cast(0) || relative > F::cast(1) { + return Err(ConfigurationError::RadiusInvalid); + } let dim = V::dim(None); let samples = match poisson_type { 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 + Ok((max_radii / F::cast(samples)).powf(F::cast(1) / F::cast(dim)) * relative) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6cfefa2b..d0c3a696 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,6 @@ //! Helper functions that poisson uses. -use {Builder, Type, Vector, Float}; +use {PoissonConfiguration, Type, Vector, Float}; use num::NumCast; @@ -188,7 +188,7 @@ pub fn index_to_sample(value: &V, side: usize) -> V } pub fn is_disk_free(grid: &Grid, - poisson: &Builder, + poisson: &PoissonConfiguration, index: V, level: usize, sample: V, @@ -207,7 +207,7 @@ pub fn is_disk_free(grid: &Grid, is_valid(poisson, outside, sample) } -pub fn is_valid(poisson: &Builder, samples: &[V], sample: V) -> bool +pub fn is_valid(poisson: &PoissonConfiguration, samples: &[V], sample: V) -> bool where F: Float, V: Vector { diff --git a/tests/adding.rs b/tests/adding.rs index 8723b192..3adc5fc4 100644 --- a/tests/adding.rs +++ b/tests/adding.rs @@ -24,8 +24,8 @@ fn adding_valid_start_works() { let relative_radius = 0.8; let rand = XorShiftRng::from_seed([0, 1, 1, 2]); let prefiller = |_| { - let mut pre = Builder::<_, Vect>::with_samples(samples, relative_radius, Type::Normal) - .build(rand.clone(), algorithm::Ebeida) + let mut pre = Builder::<_, Vect>::new().with_poisson_type(Type::Normal).with_samples(samples, relative_radius) + .build(rand.clone(), algorithm::Ebeida).unwrap() .into_iter() .take(25); move |_| pre.next() @@ -39,8 +39,8 @@ fn adding_valid_middle_works() { let relative_radius = 0.8; let rand = XorShiftRng::from_seed([1, 1, 2, 3]); let prefiller = |_| { - let prefiller = Builder::<_, Vect>::with_samples(samples, relative_radius, Type::Normal) - .build(rand.clone(), algorithm::Ebeida); + let prefiller = Builder::<_, Vect>::new().with_poisson_type(Type::Normal).with_samples(samples, relative_radius) + .build(rand.clone(), algorithm::Ebeida).unwrap(); let mut pre = repeat(None) .take(25) .chain(prefiller @@ -93,8 +93,8 @@ fn completely_filled_works() { let relative_radius = 0.8; let rand = XorShiftRng::from_seed([0, 1, 1, 2]); let prefiller = |_| { - let mut pre = Builder::<_, Vect>::with_samples(samples, relative_radius, Type::Normal) - .build(rand.clone(), algorithm::Ebeida) + let mut pre = Builder::<_, Vect>::new().with_poisson_type(Type::Normal).with_samples(samples, relative_radius) + .build(rand.clone(), algorithm::Ebeida).unwrap() .into_iter(); move |_| pre.next() }; diff --git a/tests/common.rs b/tests/common.rs index 1ec4ded0..e433aa27 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,5 +1,5 @@ extern crate poisson; -use poisson::{Type, Builder}; +use poisson::{Type, Builder, ConfigurationError}; extern crate rand; @@ -8,15 +8,13 @@ use na::Vec2 as naVec2; pub type Vec2 = naVec2; #[test] -#[should_panic] fn test_normal_too_small_radius() { - let _ = Builder::<_, Vec2>::with_radius(0.0, Type::Normal); + assert_eq!(Err(ConfigurationError::RadiusInvalid), Builder::<_, Vec2>::new().with_radius(0.0).error()); } #[test] -#[should_panic] fn test_normal_too_large_radius() { - let _ = Builder::<_, Vec2>::with_radius(2f64.sqrt() / 2.0 + 0.0001, Type::Normal); + assert_eq!(Err(ConfigurationError::RadiusInvalid), Builder::<_, Vec2>::new().with_radius(2f64.sqrt() / 2.0 + 0.0001).error()); } // #[test] diff --git a/tests/dim2.rs b/tests/dim2.rs index a3cf8cfd..9ddeffc9 100644 --- a/tests/dim2.rs +++ b/tests/dim2.rs @@ -16,13 +16,13 @@ use helper::test_with_samples; #[test] fn test_one_sample_works() { let rand = XorShiftRng::from_seed([1, 2, 3, 4]); - let builder = Builder::<_, Vect>::with_samples(1, 0.8, Normal); - let builder = builder.build(rand, algorithm::Ebeida); + let builder = Builder::<_, Vect>::new().with_poisson_type(Normal).with_samples(1, 0.8); + let builder = builder.build(rand, algorithm::Ebeida).unwrap(); builder.into_iter().collect::>(); let rand = XorShiftRng::from_seed([1, 2, 3, 4]); - let builder = Builder::<_, Vect>::with_samples(1, 0.8, Normal); - let builder = builder.build(rand, algorithm::Bridson); + let builder = Builder::<_, Vect>::new().with_poisson_type(Normal).with_samples(1, 0.8); + let builder = builder.build(rand, algorithm::Bridson).unwrap(); builder.into_iter().collect::>(); } diff --git a/tests/helper/mod.rs b/tests/helper/mod.rs index 71cbd1b2..7d6641b4 100644 --- a/tests/helper/mod.rs +++ b/tests/helper/mod.rs @@ -49,7 +49,7 @@ fn test_algo<'r, T, F, I, A>(samples: usize, relative_radius: f64, seeds: u32, p for i in 0..seeds { let mut prefilled = vec![]; let rand = XorShiftRng::from_seed([i + 1, seeds - i + 1, (i + 1) * (i + 1), 1]); - let mut poisson_iter = Builder::with_samples(samples, relative_radius, ptype).build(rand, algo).into_iter(); + let mut poisson_iter = Builder::new().with_poisson_type(ptype).with_samples(samples, relative_radius).build(rand, algo).unwrap().into_iter(); let mut poisson = vec![]; let mut prefill = (prefiller)(poisson_iter.radius()); let mut last = None;