# HG changeset patch # User unC0Rr # Date 1675245525 -3600 # Node ID c5684cc62de8f3a963da1faf718510a0abbd68ac # Parent 6e22f4390b7e2a76811736a76b38572aa3fe68dd Switch to Vec2D in wavefront algorithm diff -r 6e22f4390b7e -r c5684cc62de8 rust/land2d/src/lib.rs --- a/rust/land2d/src/lib.rs Mon Jan 30 15:50:14 2023 +0100 +++ b/rust/land2d/src/lib.rs Wed Feb 01 10:58:45 2023 +0100 @@ -19,7 +19,7 @@ let play_box = Rect::from_size(top_left, *play_size); Self { play_box, - pixels: vec2d::Vec2D::new(real_size.size(), fill_value), + pixels: vec2d::Vec2D::new(&real_size.size(), fill_value), mask: real_size.to_mask(), } } diff -r 6e22f4390b7e -r c5684cc62de8 rust/landgen/Cargo.toml --- a/rust/landgen/Cargo.toml Mon Jan 30 15:50:14 2023 +0100 +++ b/rust/landgen/Cargo.toml Wed Feb 01 10:58:45 2023 +0100 @@ -7,4 +7,5 @@ [dependencies] integral-geometry = { path = "../integral-geometry" } land2d = { path = "../land2d" } +vec2d = { path = "../vec2d" } itertools = "0.7.8" diff -r 6e22f4390b7e -r c5684cc62de8 rust/landgen/src/wavefront_collapse.rs --- a/rust/landgen/src/wavefront_collapse.rs Mon Jan 30 15:50:14 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -use integral_geometry::Size; -use land2d::Land2D; -use std::collections::HashMap; - -#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] -enum Tile { - Empty, - Outside, - Numbered(u32), -} - -impl Tile { - fn is(&self, i: u32) -> bool { - *self == Tile::Numbered(i) - } - - fn is_empty(&self) -> bool { - match self { - Tile::Empty => true, - Tile::Outside => true, - _ => false, - } - } - - fn is_empty_or(&self, i: u32) -> bool { - match self { - Tile::Numbered(n) => *n == i, - Tile::Empty => true, - _ => false, - } - } - - fn is_void_or(&self, i: u32) -> bool { - match self { - Tile::Numbered(n) => *n == i, - _ => true, - } - } -} - -impl Default for Tile { - fn default() -> Self { - Tile::Outside - } -} - -struct CollapseRule { - to_tile: Tile, - condition: fn([Tile; 4]) -> bool, -} - -#[derive(Default)] -struct WavefrontCollapse { - rules: HashMap>, -} - -impl WavefrontCollapse { - pub fn generate_map, F: FnOnce(&mut Land2D)>( - &mut self, - map_size: &Size, - seed_fn: F, - random_numbers: &mut I, - ) -> Land2D { - let mut land = Land2D::new(map_size, Tile::Empty); - - seed_fn(&mut land); - - while self.collapse_step(&mut land, random_numbers) {} - - land - } - - fn add_rule(&mut self, from_tile: Tile, to_tile: Tile, condition: fn([Tile; 4]) -> bool) { - let rule = CollapseRule { to_tile, condition }; - self.rules - .entry(from_tile) - .or_insert_with(Vec::new) - .push(rule); - } - - fn collapse_step>( - &self, - land: &mut Land2D, - random_numbers: &mut I, - ) -> bool { - let mut collapse_occurred = false; - for x in 0..land.width() { - for y in 0..land.height() { - let current_tile = land.map(y as i32, x as i32, |p| *p); - - if let Some(rules) = self.rules.get(¤t_tile) { - for rule in rules - .iter() - .cycle() - .skip( - random_numbers.next().unwrap_or_default() as usize % (rules.len() + 1), - ) - .take(rules.len()) - { - let neighbors = self.get_neighbors(&land, x, y); - let have_neighbors = neighbors.iter().any(|t| !t.is_empty()); - if have_neighbors && (rule.condition)(neighbors) { - land.map(y as i32, x as i32, |p| *p = rule.to_tile); - collapse_occurred = true; - break; - } - } - } - } - } - - collapse_occurred - } - - fn get_neighbors(&self, land: &Land2D, x: usize, y: usize) -> [Tile; 4] { - [ - land.get(y as i32, x as i32 + 1), - land.get(y as i32 + 1, x as i32), - land.get(y as i32, x as i32 - 1), - land.get(y as i32 - 1, x as i32), - ] - } -} - -#[cfg(test)] -mod tests { - use super::{CollapseRule, Tile, WavefrontCollapse}; - use integral_geometry::Size; - use land2d::Land2D; - use std::collections::HashMap; - - #[test] - fn test_wavefront_collapse() { - let size = Size::new(4, 4); - let mut rnd = [0u32; 64].into_iter(); - let mut wfc = WavefrontCollapse::default(); - - let empty_land = Land2D::new(&size, Tile::Empty); - let no_rules_land = wfc.generate_map(&size, |l| {}, &mut rnd); - - assert_eq!(empty_land.raw_pixels(), no_rules_land.raw_pixels()); - - wfc.add_rule(Tile::Empty, Tile::Numbered(0), |neighbors| { - neighbors.iter().filter(|&n| *n == Tile::Empty).count() >= 2 - }); - let ruled_map = wfc.generate_map(&size, |l| {}, &mut rnd); - - assert_eq!(ruled_map.raw_pixels(), empty_land.raw_pixels()); - } -} diff -r 6e22f4390b7e -r c5684cc62de8 rust/landgen/src/wavefront_collapse/generator.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/landgen/src/wavefront_collapse/generator.rs Wed Feb 01 10:58:45 2023 +0100 @@ -0,0 +1,31 @@ +use super::wavefront_collapse::WavefrontCollapse; +use super::tile_image::TileImage; +use crate::{LandGenerationParameters, LandGenerator}; + +pub struct WavefrontCollapseLandGenerator { + wfc: WavefrontCollapse, + tiles: Vec, +} + +impl WavefrontCollapseLandGenerator { + pub fn new() -> Self { + Self { + wfc: WavefrontCollapse::default(), + tiles: Vec::new() + } + } + + pub fn load_template() { + + } +} + +impl LandGenerator for WavefrontCollapseLandGenerator { + fn generate_land>( + &self, + parameters: &LandGenerationParameters, + random_numbers: &mut I, + ) -> land2d::Land2D { + todo!() + } +} diff -r 6e22f4390b7e -r c5684cc62de8 rust/landgen/src/wavefront_collapse/mod.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/landgen/src/wavefront_collapse/mod.rs Wed Feb 01 10:58:45 2023 +0100 @@ -0,0 +1,3 @@ +pub mod generator; +mod tile_image; +mod wavefront_collapse; diff -r 6e22f4390b7e -r c5684cc62de8 rust/landgen/src/wavefront_collapse/tile_image.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/landgen/src/wavefront_collapse/tile_image.rs Wed Feb 01 10:58:45 2023 +0100 @@ -0,0 +1,34 @@ +use land2d::Land2D; +use std::rc::Rc; + +pub struct TileImage { + image: Rc>, + flip: bool, + mirror: bool, +} + +impl TileImage { + pub fn new(flip: bool, mirror: bool) -> Self { + Self { + image: todo!(), + flip, + mirror, + } + } + + pub fn mirrored(&self) -> Self { + Self { + image: self.image.clone(), + flip: self.flip, + mirror: !self.mirror + } + } + + pub fn flipped(&self) -> Self { + Self { + image: self.image.clone(), + flip: !self.flip, + mirror: self.mirror + } + } +} diff -r 6e22f4390b7e -r c5684cc62de8 rust/landgen/src/wavefront_collapse/wavefront_collapse.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/landgen/src/wavefront_collapse/wavefront_collapse.rs Wed Feb 01 10:58:45 2023 +0100 @@ -0,0 +1,149 @@ +use integral_geometry::Size; +use vec2d::Vec2D; +use std::collections::HashMap; + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub enum Tile { + Empty, + Outside, + Numbered(u32), +} + +impl Tile { + fn is(&self, i: u32) -> bool { + *self == Tile::Numbered(i) + } + + fn is_empty(&self) -> bool { + match self { + Tile::Empty => true, + Tile::Outside => true, + _ => false, + } + } + + fn is_empty_or(&self, i: u32) -> bool { + match self { + Tile::Numbered(n) => *n == i, + Tile::Empty => true, + _ => false, + } + } + + fn is_void_or(&self, i: u32) -> bool { + match self { + Tile::Numbered(n) => *n == i, + _ => true, + } + } +} + +impl Default for Tile { + fn default() -> Self { + Tile::Outside + } +} + +struct CollapseRule { + to_tile: Tile, + condition: fn([Tile; 4]) -> bool, +} + +#[derive(Default)] +pub struct WavefrontCollapse { + rules: HashMap>, +} + +impl WavefrontCollapse { + pub fn generate_map, F: FnOnce(&mut Vec2D)>( + &mut self, + map_size: &Size, + seed_fn: F, + random_numbers: &mut I, + ) -> Vec2D { + let mut land = Vec2D::new(&map_size, Tile::Empty); + + seed_fn(&mut land); + + while self.collapse_step(&mut land, random_numbers) {} + + land + } + + fn add_rule(&mut self, from_tile: Tile, to_tile: Tile, condition: fn([Tile; 4]) -> bool) { + let rule = CollapseRule { to_tile, condition }; + self.rules + .entry(from_tile) + .or_insert_with(Vec::new) + .push(rule); + } + + fn collapse_step>( + &self, + land: &mut Vec2D, + random_numbers: &mut I, + ) -> bool { + let mut collapse_occurred = false; + for x in 0..land.width() { + for y in 0..land.height() { + let current_tile = land.get(y, x).expect("valid iteration range"); + + if let Some(rules) = self.rules.get(¤t_tile) { + for rule in rules + .iter() + .cycle() + .skip( + random_numbers.next().unwrap_or_default() as usize % (rules.len() + 1), + ) + .take(rules.len()) + { + let neighbors = self.get_neighbors(&land, x, y); + let have_neighbors = neighbors.iter().any(|t| !t.is_empty()); + if have_neighbors && (rule.condition)(neighbors) { + *land.get_mut(y, x).expect("valid iteration range") = rule.to_tile; + collapse_occurred = true; + break; + } + } + } + } + } + + collapse_occurred + } + + fn get_neighbors(&self, land: &Vec2D, x: usize, y: usize) -> [Tile; 4] { + [ + land.get(y, x + 1).map(|p| *p).unwrap_or_default(), + land.get(y + 1, x).map(|p| *p).unwrap_or_default(), + land.get(y, x.wrapping_sub(1)).map(|p| *p).unwrap_or_default(), + land.get(y.wrapping_sub(1), x).map(|p| *p).unwrap_or_default(), + ] + } +} + +#[cfg(test)] +mod tests { + use super::{Tile, WavefrontCollapse}; + use integral_geometry::Size; + use vec2d::Vec2D; + + #[test] + fn test_wavefront_collapse() { + let size = Size::new(4, 4); + let mut rnd = [0u32; 64].into_iter(); + let mut wfc = WavefrontCollapse::default(); + + let empty_land = Vec2D::new(&size, Tile::Empty); + let no_rules_land = wfc.generate_map(&size, |_| {}, &mut rnd); + + assert_eq!(empty_land.as_slice(), no_rules_land.as_slice()); + + wfc.add_rule(Tile::Empty, Tile::Numbered(0), |neighbors| { + neighbors.iter().filter(|&n| *n == Tile::Empty).count() >= 2 + }); + let ruled_map = wfc.generate_map(&size, |_| {}, &mut rnd); + + assert_eq!(ruled_map.as_slice(), empty_land.as_slice()); + } +} diff -r 6e22f4390b7e -r c5684cc62de8 rust/vec2d/src/lib.rs --- a/rust/vec2d/src/lib.rs Mon Jan 30 15:50:14 2023 +0100 +++ b/rust/vec2d/src/lib.rs Wed Feb 01 10:58:45 2023 +0100 @@ -1,8 +1,8 @@ +use integral_geometry::Size; use std::{ ops::{Index, IndexMut}, - slice::SliceIndex + slice::SliceIndex, }; -use integral_geometry::Size; #[derive(Debug)] pub struct Vec2D { @@ -34,7 +34,7 @@ } } -impl Vec2D { +impl Vec2D { #[inline] pub fn width(&self) -> usize { self.size.width @@ -52,8 +52,11 @@ } impl Vec2D { - pub fn new(size: Size, value: T) -> Self { - Self { size, data: vec![value; size.area()] } + pub fn new(size: &Size, value: T) -> Self { + Self { + size: *size, + data: vec![value; size.area()], + } } #[inline] @@ -68,21 +71,41 @@ #[inline] pub fn get(&self, row: usize, column: usize) -> Option<&>::Output> { - self.data.get(row * self.width() + column) + if row < self.height() && column < self.width() { + Some(unsafe { self.data.get_unchecked(row * self.width() + column) }) + } else { + None + } } #[inline] - pub fn get_mut(&mut self, row: usize, column: usize) -> Option<&mut >::Output> { - self.data.get_mut(row * self.size.width + column) + pub fn get_mut( + &mut self, + row: usize, + column: usize, + ) -> Option<&mut >::Output> { + if row < self.height() && column < self.width() { + Some(unsafe { self.data.get_unchecked_mut(row * self.size.width + column) }) + } else { + None + } } #[inline] - pub unsafe fn get_unchecked(&self, row: usize, column: usize) -> &>::Output { + pub unsafe fn get_unchecked( + &self, + row: usize, + column: usize, + ) -> &>::Output { self.data.get_unchecked(row * self.width() + column) } #[inline] - pub unsafe fn get_unchecked_mut(&mut self, row: usize, column: usize) -> &mut >::Output { + pub unsafe fn get_unchecked_mut( + &mut self, + row: usize, + column: usize, + ) -> &mut >::Output { self.data.get_unchecked_mut(row * self.size.width + column) } @@ -99,10 +122,7 @@ #[inline] pub unsafe fn as_bytes(&self) -> &[u8] { - use std::{ - slice, - mem - }; + use std::{mem, slice}; slice::from_raw_parts( self.data.as_ptr() as *const u8,