diff --git a/Cargo.toml b/Cargo.toml index 5817e6f..ae1fb38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,8 @@ resolver = "2" # this is required to render examples with wgpu members = [ "wfc", "wfc-image", - "animation-helper", + "animation-helper", + "benchmarks", ] [profile.release] diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml new file mode 100644 index 0000000..43447b8 --- /dev/null +++ b/benchmarks/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "benchmarks" +version = "0.1.0" +edition = "2021" + +[profile.dev] +opt-level = 3 + +[dependencies] +wfc = { path = "../wfc" } +wfc_image = { path = "../wfc-image" } +image = { version = "0.24", default-features = false, features = ["png"] } +coord_2d = "0.3" +grid_2d = "0.15" +rand = "0.8" \ No newline at end of file diff --git a/benchmarks/src/base.rs b/benchmarks/src/base.rs new file mode 100644 index 0000000..4a4c1d9 --- /dev/null +++ b/benchmarks/src/base.rs @@ -0,0 +1,115 @@ +extern crate test; + +use std::num::NonZeroU32; + +use grid_2d::Grid; +use rand::{rngs::StdRng, SeedableRng}; +use test::Bencher; +use wfc::{ + overlapping::OverlappingPatterns, Context, Coord, RunBorrow, RunOwn, Size, Wave, +}; +use wfc_image::WrapXY; + +const TEST_GRID_10X10: [[u8; 10]; 10] = [ + [0, 1, 1, 2, 0, 1, 1, 2, 2, 1], + [1, 2, 2, 2, 1, 3, 1, 1, 1, 1], + [0, 1, 2, 2, 2, 1, 3, 1, 1, 1], + [0, 1, 1, 2, 2, 2, 1, 3, 1, 1], + [0, 0, 1, 1, 0, 1, 1, 0, 0, 1], + [0, 1, 1, 1, 0, 1, 1, 0, 0, 1], + [0, 1, 1, 2, 0, 1, 1, 2, 2, 1], + [2, 0, 1, 1, 2, 0, 1, 1, 2, 1], + [1, 1, 1, 1, 2, 2, 2, 1, 1, 1], + [2, 2, 2, 2, 1, 2, 2, 2, 2, 1], +]; + +fn generate_input_grid( + data: &[[u8; WIDTH]; HEIGHT], +) -> Grid { + let mut input = Grid::::new_default(Size::new(WIDTH as u32, HEIGHT as u32)); + for (y, row) in data.iter().enumerate() { + for (x, val) in row.iter().enumerate() { + let cell = input.get_mut(Coord::new(x as i32, y as i32)).unwrap(); + *cell = *val; + } + } + input +} + +#[bench] +fn bench_gen_pattern_3x3_from_10x10(bencher: &mut Bencher) { + let input = generate_input_grid(&TEST_GRID_10X10); + + bencher.iter(|| { + OverlappingPatterns::new_original_orientation( + input.clone(), + NonZeroU32::new(3).unwrap(), + ); + }) +} + +#[bench] +fn bench_10x10_pattern_3x3_borrow(bencher: &mut Bencher) { + let input = generate_input_grid(&TEST_GRID_10X10); + + let global_stats = OverlappingPatterns::new_original_orientation( + input.clone(), + NonZeroU32::new(3).unwrap(), + ) + .global_stats(); + + bencher.iter(|| { + let mut rng = StdRng::seed_from_u64(21371); + + let mut wave = Wave::new(Size::new(10, 10)); + let mut context = Context::new(); + + let mut run = + RunBorrow::new_wrap(&mut context, &mut wave, &global_stats, WrapXY, &mut rng); + + run.collapse(&mut rng).unwrap(); + }); +} + +#[bench] +fn bench_20x20_pattern_3x3_borrow(bencher: &mut Bencher) { + let input = generate_input_grid(&TEST_GRID_10X10); + + let global_stats = OverlappingPatterns::new_original_orientation( + input.clone(), + NonZeroU32::new(3).unwrap(), + ) + .global_stats(); + + bencher.iter(|| { + let mut rng = StdRng::seed_from_u64(21371); + + let mut wave = Wave::new(Size::new(20, 20)); + let mut context = Context::new(); + + let mut run = + RunBorrow::new_wrap(&mut context, &mut wave, &global_stats, WrapXY, &mut rng); + + run.collapse(&mut rng).unwrap(); + }); +} + +#[bench] +fn bench_10x10_pattern_3x3_own(bencher: &mut Bencher) { + let input = generate_input_grid(&TEST_GRID_10X10); + + let global_stats = OverlappingPatterns::new_original_orientation( + input.clone(), + NonZeroU32::new(3).unwrap(), + ) + .global_stats(); + + bencher.iter(|| { + let mut rng = StdRng::seed_from_u64(21371); + + let mut run = + RunOwn::new_wrap(Size::new(10, 10), &global_stats, WrapXY, &mut rng); + + run.collapse(&mut rng).unwrap(); + }); +} diff --git a/benchmarks/src/image.rs b/benchmarks/src/image.rs new file mode 100644 index 0000000..444796a --- /dev/null +++ b/benchmarks/src/image.rs @@ -0,0 +1,112 @@ +extern crate test; + +use std::num::NonZeroU32; + +use rand::{rngs::StdRng, SeedableRng}; +use test::Bencher; +use wfc::orientation; +use wfc::{ForbidNothing, Size}; +use wfc_image::{generate_image_with_rng, retry, ImagePatterns, WrapXY}; + +const EXAMPLE: &str = "../wfc-image/examples/rooms.png"; + +#[bench] +fn bench_gen_pattern_3x3(bencher: &mut Bencher) { + let input_image = image::open(EXAMPLE).unwrap(); + let orientations = &[orientation::Orientation::Original]; + + bencher.iter(|| { + ImagePatterns::new(&input_image, NonZeroU32::new(3).unwrap(), orientations); + }) +} + +#[bench] +fn bench_10x10_pattern_3x3(bencher: &mut Bencher) { + let input_image = image::open(EXAMPLE).unwrap(); + let orientations = &[orientation::Orientation::Original]; + let output_size = Size::new(10, 10); + + bencher.iter(|| { + let mut rng = StdRng::seed_from_u64(2137); + + generate_image_with_rng( + &input_image, + NonZeroU32::new(3).unwrap(), + output_size, + orientations, + WrapXY, + ForbidNothing, + retry::NumTimes(0), + &mut rng, + ) + .unwrap(); + }) +} + +#[bench] +fn bench_10x10_pattern_4x4(bencher: &mut Bencher) { + let input_image = image::open(EXAMPLE).unwrap(); + let orientations = &[orientation::Orientation::Original]; + let output_size = Size::new(10, 10); + + bencher.iter(|| { + let mut rng = StdRng::seed_from_u64(21371); + + generate_image_with_rng( + &input_image, + NonZeroU32::new(4).unwrap(), + output_size, + orientations, + WrapXY, + ForbidNothing, + retry::NumTimes(0), + &mut rng, + ) + .unwrap(); + }) +} + +#[bench] +fn bench_10x10_pattern_3x3_orientations_all(bencher: &mut Bencher) { + let input_image = image::open(EXAMPLE).unwrap(); + let output_size = Size::new(10, 10); + + bencher.iter(|| { + let mut rng = StdRng::seed_from_u64(2137); + + generate_image_with_rng( + &input_image, + NonZeroU32::new(3).unwrap(), + output_size, + &orientation::ALL, + WrapXY, + ForbidNothing, + retry::NumTimes(0), + &mut rng, + ) + .unwrap(); + }) +} + +#[bench] +fn bench_20x20_pattern_3x3(bencher: &mut Bencher) { + let input_image = image::open(EXAMPLE).unwrap(); + let orientations = &[orientation::Orientation::Original]; + let output_size = Size::new(20, 20); + + bencher.iter(|| { + let mut rng = StdRng::seed_from_u64(2137); + + generate_image_with_rng( + &input_image, + NonZeroU32::new(3).unwrap(), + output_size, + orientations, + WrapXY, + ForbidNothing, + retry::NumTimes(0), + &mut rng, + ) + .unwrap(); + }) +} diff --git a/benchmarks/src/lib.rs b/benchmarks/src/lib.rs new file mode 100644 index 0000000..4078218 --- /dev/null +++ b/benchmarks/src/lib.rs @@ -0,0 +1,6 @@ +#![feature(test)] + +#[cfg(test)] +mod base; +#[cfg(test)] +mod image; diff --git a/wfc-image/examples/anchor.rs b/wfc-image/examples/anchor.rs index 371f119..90aa641 100644 --- a/wfc-image/examples/anchor.rs +++ b/wfc-image/examples/anchor.rs @@ -60,11 +60,10 @@ impl ForbidPattern for Forbid { fn forbid(&mut self, fi: &mut ForbidInterface, rng: &mut R) { let output_size = fi.wave_size(); (0..(output_size.width() as i32)) - .map(|x| Coord::new(x, output_size.height() as i32 - self.offset as i32)) + .map(|x| Coord::new(x, output_size.height() as i32 - self.offset)) .chain( - (0..(output_size.width() as i32)).map(|y| { - Coord::new(output_size.width() as i32 - self.offset as i32, y) - }), + (0..(output_size.width() as i32)) + .map(|y| Coord::new(output_size.width() as i32 - self.offset, y)), ) .for_each(|coord| { self.pattern_ids.iter().for_each(|&pattern_id| { diff --git a/wfc-image/src/lib.rs b/wfc-image/src/lib.rs index 852df60..f0a711d 100644 --- a/wfc-image/src/lib.rs +++ b/wfc-image/src/lib.rs @@ -190,6 +190,11 @@ impl retry::ImageRetry for retry::ParNumTimes { } } +/// Generate image with Wave Function Collapse algorithm using provided `rng` +/// state. +/// +/// For more detailed documentation see [`generate_image`]. +#[allow(clippy::too_many_arguments)] pub fn generate_image_with_rng( image: &DynamicImage, pattern_size: NonZeroU32, @@ -213,6 +218,45 @@ where ) } +/// Generate image with Wave Function Collapse algorithm using random seed. +/// +/// For generation using fixed [`Rng`] see [`generate_image_with_rng`]. +/// +/// # Arguments +/// - `image` - input [`DynamicImage`]. Source of [`ImagePatterns`] which will +/// be used while building the output image. +/// - `pattern_size` - size of the generated patterns. Smaller size means that +/// the patterns in output image will be less similar to the input ones. +/// - `output_size` - [`Size`] of the output image. +/// - `orientations` - collection of [`Orientation`], signifying which +/// transformations can be made on generated *patterns* while building +/// the output image. +/// - `wrap` - [`Wrap`] strategy for the *patterns*. +/// - `forbid` - rules restricting the positioning of the patterns in the output +/// image. +/// - `retry` - the retrying strategy upon encountering error during collapse. +/// +/// # Example +/// ``` +/// use std::num::NonZeroU32; +/// +/// use wfc::{Orientation, Size, ForbidNothing}; +/// use wfc_image::{generate_image, retry, ImagePatterns, WrapXY}; +/// +/// let input_image = image::open("../wfc-image/examples/rooms.png").unwrap(); +/// let orientations = &[Orientation::Original]; +/// let output_size = Size::new(10, 10); +/// +/// generate_image( +/// &input_image, +/// NonZeroU32::new(3).unwrap(), +/// output_size, +/// orientations, +/// WrapXY, +/// ForbidNothing, +/// retry::NumTimes(10) +/// ).expect("couldn't generate after 10 retries"); +/// ``` pub fn generate_image( image: &DynamicImage, pattern_size: NonZeroU32, diff --git a/wfc/examples/observe_by_step.rs b/wfc/examples/observe_by_step.rs new file mode 100644 index 0000000..118ad44 --- /dev/null +++ b/wfc/examples/observe_by_step.rs @@ -0,0 +1,90 @@ +use std::num::NonZeroU32; + +use grid_2d::Grid; +use rand::{distributions::Uniform, rngs::StdRng, SeedableRng}; +use utils::generate_input_grid; +use wfc::{ + orientation, overlapping::OverlappingPatterns, wrap::WrapXY, Coord, Observe, RunOwn, + Size, +}; + +mod utils; + +const GRID_WIDTH: usize = 10; +const GRID_HEIGHT: usize = 10; + +fn main() { + // Let us generate a 10x10 input grid of u32. + let input = + generate_input_grid::(GRID_WIDTH, GRID_HEIGHT, Uniform::new(0, 3), 2137); + + // Create the patterns for the algorithm to generate new object. + let patterns = OverlappingPatterns::new( + input.clone(), + // Patterns will be 3x3 tiles. + NonZeroU32::new(3).unwrap(), + // Patterns will be generated in all orientations. + &orientation::ALL, + ); + + let global_stats = patterns.global_stats(); + + // Seed for reproductability. + let mut rng = StdRng::seed_from_u64(2137); + + // Create WFC runner which owns the data. + let mut runner: RunOwn = RunOwn::new( + Size::new(GRID_WIDTH as u32, GRID_HEIGHT as u32), + &global_stats, + &mut rng, + ); + + // To keep track of `WaveCells` that have been collapsed. + let mut collapsed_coords = Vec::::new(); + + // Collapse step-by-step, printing the collapsed tiles + loop { + match runner.step(&mut rng) { + Ok(observe) => { + for (coord, cell) in runner.wave_cell_ref_enumerate() { + if let Ok(collapsed_pattern) = cell.as_ref().chosen_pattern_id() { + if !collapsed_coords.contains(&coord) { + let collapsed_value = + patterns.pattern_top_left_value(collapsed_pattern); + println!( + "collapsed value: {collapsed_value} at coord: {coord:?}" + ); + collapsed_coords.push(coord); + } + } + } + if observe == Observe::Complete { + break; + } + } + Err(err) => { + panic!("propagation error!: {err:?}"); + } + } + } + + // Collapse the WFC to generate new grid, retrying up to 10 times if met with contradiction. + let wave = runner.into_wave(); + + // Construct output grid. + // + // The `Wave` consists of `WaveCells`, which hold information about the pattern chosen for given grid position. + // Each of patterns give us information about `top_left_value`, which is the value that will be present in our + // output grid. + let mut output = Grid::new_default(wave.grid().size()); + + wave.grid().enumerate().for_each(|(Coord { x, y }, cell)| { + if let Ok(chosed_pattern_id) = cell.chosen_pattern_id() { + *output.get_mut(Coord { x, y }).unwrap() = + *patterns.pattern_top_left_value(chosed_pattern_id); + } + }); + + // Output and input grids are different. + assert_ne!(input, output); +} diff --git a/wfc/examples/retrying_complete.rs b/wfc/examples/retrying_complete.rs new file mode 100644 index 0000000..3984793 --- /dev/null +++ b/wfc/examples/retrying_complete.rs @@ -0,0 +1,64 @@ +use std::num::NonZeroU32; + +use grid_2d::Grid; +use rand::{distributions::Uniform, rngs::StdRng, SeedableRng}; +use utils::generate_input_grid; +use wfc::{ + orientation, overlapping::OverlappingPatterns, retry, wrap::WrapXY, Context, Coord, + RunBorrow, Size, Wave, +}; + +mod utils; + +const GRID_WIDTH: usize = 10; +const GRID_HEIGHT: usize = 10; +const RETRY_TIMES: usize = 10; + +fn main() { + // Let us generate a 10x10 input grid of u32. + let input = + generate_input_grid::(GRID_WIDTH, GRID_HEIGHT, Uniform::new(0, 5), 2137); + + // Create the patterns for the algorithm to generate new object. + let patterns = OverlappingPatterns::new( + input.clone(), + // Patterns will be 3x3 tiles. + NonZeroU32::new(3).unwrap(), + // Patterns will be generated in all orientations. + &orientation::ALL, + ); + + let global_stats = patterns.global_stats(); + + // Seed for reproductability. + let mut rng = StdRng::seed_from_u64(2137); + + // Create data containers and WFC runner which borrows the data. + let mut context = Context::new(); + let mut wave = Wave::new(Size::new(GRID_WIDTH as u32, GRID_HEIGHT as u32)); + + let mut runner: RunBorrow = + RunBorrow::new(&mut context, &mut wave, &global_stats, &mut rng); + + // Collapse the WFC to generate new grid, retrying up to 10 times if met with contradiction. + runner + .collapse_retrying(retry::NumTimes(RETRY_TIMES), &mut rng) + .unwrap_or_else(|_| panic!("cannot resolve after {RETRY_TIMES} retries")); + + // Construct output grid. + // + // The `Wave` consists of `WaveCells`, which hold information about the pattern chosen for given grid position. + // Each of patterns give us information about `top_left_value`, which is the value that will be present in our + // output grid. + let mut output = Grid::new_default(wave.grid().size()); + + wave.grid().enumerate().for_each(|(Coord { x, y }, cell)| { + if let Ok(chosed_pattern_id) = cell.chosen_pattern_id() { + *output.get_mut(Coord { x, y }).unwrap() = + *patterns.pattern_top_left_value(chosed_pattern_id); + } + }); + + // Output and input grids are different. + assert_ne!(input, output); +} diff --git a/wfc/examples/utils/mod.rs b/wfc/examples/utils/mod.rs new file mode 100644 index 0000000..f00bbf2 --- /dev/null +++ b/wfc/examples/utils/mod.rs @@ -0,0 +1,26 @@ +use grid_2d::Grid; +use rand::{ + distributions::{uniform::SampleUniform, Distribution, Uniform}, + rngs::StdRng, + SeedableRng, +}; +use wfc::{Coord, Size}; + +pub fn generate_input_grid( + width: usize, + height: usize, + options: Uniform, + seed: u64, +) -> Grid { + let mut rng = StdRng::seed_from_u64(seed); + let mut grid = Grid::new_default(Size::new(width as u32, height as u32)); + + for x in 0..width { + for y in 0..height { + let cell = grid.get_mut(Coord::new(x as i32, y as i32)).unwrap(); + *cell = options.sample(&mut rng); + } + } + + grid +} diff --git a/wfc/src/lib.rs b/wfc/src/lib.rs index c6d605a..6328cd2 100644 --- a/wfc/src/lib.rs +++ b/wfc/src/lib.rs @@ -1,3 +1,21 @@ +//! Wave Function Collapse algorithm implementation for arbitrary grids. +//! +//! Functionality in this crate allows generating two-dimensional grid of some arbitrary data using patterns from sample +//! data. If you are looking for implementation over images, there is `wfc-image` crate which is tailored to be used +//! for it. +//! +//! If your aim is to generate grids of some custom type, you can implement WFC algorithm using runner in this crate. +//! General workflow: +//! +//! 1. Generate [`OverlappingPatterns`](crate::overlapping::OverlappingPatterns) using sample [`Grid`](grid_2d::Grid). +//! 2. Use [`GlobalStats`] from overlapping patterns to create either: +//! - runner owning the data: [`RunOwn`] +//! - runner borrowing the data: [`RunBorrow`] +//! Setup of additional objects may be needed depending on your choice. +//! 3. Run the algorithm, either completely to its completion (`collapse()` / `collapse_retrying()`) or on step by step basis +//! (`step()`). +//! 4. Convert generated [`Wave`] into output of your choice. + pub mod orientation; pub mod overlapping; pub mod retry; diff --git a/wfc/src/orientation.rs b/wfc/src/orientation.rs index 719269f..0444fff 100644 --- a/wfc/src/orientation.rs +++ b/wfc/src/orientation.rs @@ -3,37 +3,53 @@ use coord_2d::{Coord, Size}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum Orientation { - /// ##. + /// ``` + /// xx. /// ... /// ... + /// ``` Original, - /// ..# - /// ..# + /// ``` + /// ..x + /// ..x /// ... + /// ``` Clockwise90, + /// ``` /// ... /// ... - /// .## + /// .xx + /// ``` Clockwise180, + /// ``` /// ... - /// #.. - /// #.. + /// x.. + /// x.. + /// ``` Clockwise270, - /// #.. - /// #.. + /// ``` + /// x.. + /// x.. /// ... + /// ``` DiagonallyFlipped, - /// .## + /// ``` + /// .xx /// ... /// ... + /// ``` DiagonallyFlippedClockwise90, + /// ``` /// ... - /// ..# - /// ..# + /// ..x + /// ..x + /// ``` DiagonallyFlippedClockwise180, + /// ``` /// ... /// ... - /// ##. + /// xx. + /// ``` DiagonallyFlippedClockwise270, } @@ -79,12 +95,18 @@ pub struct OrientationTable { table: [Option; NUM_ORIENTATIONS], } -impl OrientationTable { - pub fn new() -> Self { +impl Default for OrientationTable { + fn default() -> Self { Self { table: [None, None, None, None, None, None, None, None], } } +} + +impl OrientationTable { + pub fn new() -> Self { + Default::default() + } pub fn get(&self, orientation: Orientation) -> Option<&T> { self.table[orientation as usize].as_ref() } diff --git a/wfc/src/overlapping.rs b/wfc/src/overlapping.rs index 0b64568..68dd6b0 100644 --- a/wfc/src/overlapping.rs +++ b/wfc/src/overlapping.rs @@ -143,11 +143,10 @@ impl OverlappingPatterns { pub fn id_grid_original_orientation(&self) -> Grid { let id_grid = self.id_grid(); Grid::new_fn(id_grid.size(), |coord| { - id_grid + *id_grid .get_checked(coord) .get(Orientation::Original) .expect("Missing original orientation") - .clone() }) } fn compatible_patterns<'b>( diff --git a/wfc/src/retry.rs b/wfc/src/retry.rs index e3189f7..7e309b1 100644 --- a/wfc/src/retry.rs +++ b/wfc/src/retry.rs @@ -6,7 +6,7 @@ use rand::Rng; pub trait RetryOwn: private::Sealed { type Return; - fn retry<'a, W, F, R>(&mut self, run: RunOwn<'a, W, F>, rng: &mut R) -> Self::Return + fn retry(&mut self, run: RunOwn<'_, W, F>, rng: &mut R) -> Self::Return where W: Wrap + Clone + Sync + Send, F: ForbidPattern + Clone + Sync + Send, @@ -18,11 +18,7 @@ pub struct Forever; impl RetryOwn for Forever { type Return = Wave; - fn retry<'a, W, F, R>( - &mut self, - mut run: RunOwn<'a, W, F>, - rng: &mut R, - ) -> Self::Return + fn retry(&mut self, mut run: RunOwn<'_, W, F>, rng: &mut R) -> Self::Return where W: Wrap + Clone + Sync + Send, F: ForbidPattern + Clone + Sync + Send, @@ -89,11 +85,7 @@ pub struct NumTimes(pub usize); impl RetryOwn for NumTimes { type Return = Result; - fn retry<'a, W, F, R>( - &mut self, - mut run: RunOwn<'a, W, F>, - rng: &mut R, - ) -> Self::Return + fn retry(&mut self, mut run: RunOwn<'_, W, F>, rng: &mut R) -> Self::Return where W: Wrap + Clone + Sync + Send, F: ForbidPattern + Clone + Sync + Send, @@ -200,9 +192,9 @@ impl RetryOwnAll for ParNumTimes { pub trait RetryBorrow: private::Sealed { type Return; - fn retry<'a, W, F, R>( + fn retry( &mut self, - run: &mut RunBorrow<'a, W, F>, + run: &mut RunBorrow<'_, W, F>, rng: &mut R, ) -> Self::Return where @@ -213,9 +205,9 @@ pub trait RetryBorrow: private::Sealed { impl RetryBorrow for Forever { type Return = (); - fn retry<'a, W, F, R>( + fn retry( &mut self, - run: &mut RunBorrow<'a, W, F>, + run: &mut RunBorrow<'_, W, F>, rng: &mut R, ) -> Self::Return where @@ -234,9 +226,9 @@ impl RetryBorrow for Forever { impl RetryBorrow for NumTimes { type Return = Result<(), PropagateError>; - fn retry<'a, W, F, R>( + fn retry( &mut self, - run: &mut RunBorrow<'a, W, F>, + run: &mut RunBorrow<'_, W, F>, rng: &mut R, ) -> Self::Return where diff --git a/wfc/src/wfc.rs b/wfc/src/wfc.rs index 90d1c36..91743ac 100644 --- a/wfc/src/wfc.rs +++ b/wfc/src/wfc.rs @@ -29,6 +29,9 @@ impl PatternTable { pub fn len(&self) -> usize { self.table.len() } + pub fn is_empty(&self) -> bool { + self.table.is_empty() + } pub fn drain(&mut self) -> ::std::vec::Drain { self.table.drain(..) } @@ -615,6 +618,7 @@ struct CoordEntropy { entropy_with_noise: EntropyWithNoise, } +#[allow(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for CoordEntropy { fn partial_cmp(&self, other: &Self) -> Option { other @@ -631,7 +635,7 @@ impl Ord for CoordEntropy { if self == other { return Ordering::Equal; } - return Ordering::Greater; + Ordering::Greater } } @@ -658,23 +662,21 @@ impl<'a> CellAtCoordMut<'a> { .num_ways_to_become_each_pattern .enumerate_mut() { - if pattern_id != pattern_id_to_keep { - if !num_ways_to_become_pattern.is_zero() { - num_ways_to_become_pattern.clear_all_directions(); - assert!(self.wave_cell.num_compatible_patterns >= 1); - self.wave_cell.num_compatible_patterns -= 1; - if let Some(pattern_stats) = global_stats.pattern_stats(pattern_id) { - self.wave_cell - .stats - .remove_compatible_pattern(pattern_stats); - } - propagator - .removed_patterns_to_propagate - .push(RemovedPattern { - coord: self.coord, - pattern_id, - }); + if pattern_id != pattern_id_to_keep && !num_ways_to_become_pattern.is_zero() { + num_ways_to_become_pattern.clear_all_directions(); + assert!(self.wave_cell.num_compatible_patterns >= 1); + self.wave_cell.num_compatible_patterns -= 1; + if let Some(pattern_stats) = global_stats.pattern_stats(pattern_id) { + self.wave_cell + .stats + .remove_compatible_pattern(pattern_stats); } + propagator + .removed_patterns_to_propagate + .push(RemovedPattern { + coord: self.coord, + pattern_id, + }); } } } @@ -721,7 +723,7 @@ pub struct Context { num_cells_with_more_than_one_weighted_compatible_pattern: u32, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Observe { Incomplete, Complete, @@ -759,7 +761,7 @@ impl<'a> WaveCellHandle<'a> { self.cell_at_coord_mut.remove_all_patterns_except_one( pattern_id, self.global_stats, - &mut self.propagator, + self.propagator, ); } fn forbid_pattern(&mut self, pattern_id: PatternId) { @@ -851,7 +853,7 @@ impl Context { let pattern_id = cell_at_coord.wave_cell.choose_pattern_id(global_stats, rng); cell_at_coord.remove_all_patterns_except_one( pattern_id, - &global_stats, + global_stats, &mut self.propagator, ); self.num_cells_with_more_than_one_weighted_compatible_pattern -= 1; @@ -945,6 +947,12 @@ pub struct WaveCellRef<'a> { global_stats: &'a GlobalStats, } +impl<'a> AsRef for WaveCellRef<'a> { + fn as_ref(&self) -> &WaveCell { + self.wave_cell + } +} + pub enum WaveCellRefWeight { Weight(u32), SingleNonWeightedPattern, @@ -962,20 +970,17 @@ pub struct MultipleWeightedPatternsEnumerateWeights<'a> { impl<'a> Iterator for MultipleWeightedPatternsEnumerateWeights<'a> { type Item = (PatternId, u32); fn next(&mut self) -> Option { - while let Some((pattern_id_usize, (num_ways_to_become_pattern, pattern_stats))) = - self.iter.next() + for (pattern_id_usize, (num_ways_to_become_pattern, pattern_stats)) in + self.iter.by_ref() { if num_ways_to_become_pattern.is_zero() { continue; - } else { - let pattern_id = pattern_id_usize as PatternId; - let weight = - match pattern_stats.map(|pattern_stats| pattern_stats.weight()) { - Some(weight) => weight, - None => 0, - }; - return Some((pattern_id, weight)); } + let pattern_id = pattern_id_usize as PatternId; + let weight = pattern_stats + .map(|pattern_stats| pattern_stats.weight()) + .unwrap_or(0); + return Some((pattern_id, weight)); } None } @@ -1087,7 +1092,7 @@ impl<'a, W: Wrap> RunBorrowCore<'a, W> { fn reset(&mut self, rng: &mut R) { self.wave.init(self.global_stats, rng); - self.context.init(&self.wave, self.global_stats); + self.context.init(self.wave, self.global_stats); } fn propagate(&mut self) -> Result<(), PropagateError> { diff --git a/wfc/src/wrap.rs b/wfc/src/wrap.rs index 5a6896d..b4c9047 100644 --- a/wfc/src/wrap.rs +++ b/wfc/src/wrap.rs @@ -1,5 +1,11 @@ use coord_2d::{Coord, Size}; +/// Strategy for wrapping the patterns over input and output grids. +/// +/// - [`WrapNone`] - no wrapping is done. +/// - [`WrapX`] and [`WrapY`] - wrapping either on `x` (horizontal) or `y` +/// (vertical) axis. +/// - [`WrapXY`] - wrapping around both axis. pub trait Wrap: Copy + Send + Sync + private::Sealed { #[doc(hidden)] fn normalize_coord(coord: Coord, size: Size) -> Option;