randscape 0.4.1

PCG toolkit for grids processing
Documentation
pub mod generators;
pub mod grid;
pub mod wfc;

pub mod third_party {
    pub use noise;
    pub use serde;
    pub use vek;
}

#[cfg(test)]
mod tests {
    use crate::{
        generators::{
            GridGenetator, Kernel33Generator, NoiseGenerator, OffsetLocationGenerator,
            RemapGenerator, SubGenerator, ThresholdGenerator,
        },
        grid::{Grid, GridDirection},
    };
    use image::{GrayImage, RgbImage};
    use noise::{Fbm, MultiFractal, ScalePoint, SuperSimplex, Worley};
    use serde::{Deserialize, Serialize};
    use vek::Vec2;

    const SIZE: usize = 512;

    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    enum Terrain {
        Water,
        Sand,
        Grass,
        Mountain,
    }

    fn gradient_generator(location: Vec2<usize>, size: Vec2<usize>, _: f64) -> f64 {
        let center = size / 2;
        let x = location.x.abs_diff(center.x) as f64;
        let y = location.y.abs_diff(center.y) as f64;
        let result = (x / center.x as f64).max(y / center.y as f64);
        result * result
    }

    struct OffsetsGenerator<'a> {
        pub source: &'a Grid<f64>,
        pub scale: f64,
    }

    impl GridGenetator<Vec2<isize>> for OffsetsGenerator<'_> {
        fn generate(
            &mut self,
            location: Vec2<usize>,
            _: Vec2<usize>,
            _: Vec2<isize>,
            _: &Grid<Vec2<isize>>,
        ) -> Vec2<isize> {
            let left = self
                .source
                .get(
                    self.source
                        .location_offset(location, GridDirection::West, 1)
                        .unwrap_or(location),
                )
                .unwrap_or_default();
            let right = self
                .source
                .get(
                    self.source
                        .location_offset(location, GridDirection::East, 1)
                        .unwrap_or(location),
                )
                .unwrap_or_default();
            let top = self
                .source
                .get(
                    self.source
                        .location_offset(location, GridDirection::North, 1)
                        .unwrap_or(location),
                )
                .unwrap_or_default();
            let bottom = self
                .source
                .get(
                    self.source
                        .location_offset(location, GridDirection::South, 1)
                        .unwrap_or(location),
                )
                .unwrap_or_default();
            Vec2 {
                x: ((right - left) * self.scale) as isize,
                y: ((bottom - top) * self.scale) as isize,
            }
        }
    }

    fn generate_terrain(size: Vec2<usize>) -> Grid<Terrain> {
        let mut grid = Grid::<f64>::generate(
            size,
            NoiseGenerator::new(
                Fbm::<SuperSimplex>::default()
                    .set_octaves(9)
                    .set_frequency(0.008),
            ),
        );
        grid.apply_all(RemapGenerator {
            from: -1.0..1.0,
            to: 0.0..1.0,
        });
        let gradient = grid.fork_generate(gradient_generator);
        grid.apply_all(SubGenerator { other: &gradient });
        grid.map(|_, _, value| {
            if value > 0.5 {
                Terrain::Mountain
            } else if value > 0.2 {
                Terrain::Grass
            } else if value > 0.15 {
                Terrain::Sand
            } else {
                Terrain::Water
            }
        })
    }

    fn generate_tunnels(size: Vec2<usize>) -> Grid<bool> {
        let offsets = Grid::<f64>::generate(
            size,
            NoiseGenerator::new(ScalePoint::new(SuperSimplex::default()).set_scale(0.04)),
        );
        let offsets = Grid::<Vec2<isize>>::generate(
            offsets.size(),
            OffsetsGenerator {
                source: &offsets,
                scale: 20.0,
            },
        );
        let mut thresholds = Grid::<f64>::generate(
            size,
            NoiseGenerator::new(ScalePoint::new(SuperSimplex::default()).set_scale(0.02)),
        );
        thresholds.apply_all(RemapGenerator {
            from: -1.0..1.0,
            to: 0.0..0.4,
        });
        let mut grid = Grid::<f64>::generate(
            size,
            OffsetLocationGenerator {
                generator: &mut NoiseGenerator::new(Worley::default().set_frequency(0.04)),
                offsets: &offsets,
            },
        );
        grid.apply_all(RemapGenerator {
            from: -1.0..1.0,
            to: 0.0..1.0,
        });
        grid.apply_all(Kernel33Generator::edge_detection(&grid.clone()));
        grid.apply_all(ThresholdGenerator::Constant {
            threshold: 1.0e-4,
            value_upper: 1.0,
            value_lower: 0.0,
        });
        for _ in 0..1 {
            grid.apply_all(Kernel33Generator::gaussian_blur(&grid.clone()));
        }
        grid.apply_all(ThresholdGenerator::Samples {
            thresholds: &thresholds,
            value_upper: 1.0,
            value_lower: 0.0,
        });
        grid.map(|_, _, value| value >= 0.5)
    }

    #[test]
    fn test_pcg_island() {
        let terrain = generate_terrain(SIZE.into());
        let tunnels = generate_tunnels(SIZE.into());

        let (size, buffer) = terrain.into_inner();
        let buffer = buffer
            .into_iter()
            .enumerate()
            .flat_map(|(index, value)| match value {
                Terrain::Mountain => {
                    let location = tunnels.location(index);
                    if tunnels.get(location).unwrap() {
                        [64, 64, 64]
                    } else {
                        [128, 128, 128]
                    }
                }
                Terrain::Grass => [0, 128, 0],
                Terrain::Sand => [192, 192, 128],
                Terrain::Water => [0, 0, 128],
            })
            .collect();
        let image = RgbImage::from_vec(size.x as _, size.y as _, buffer).unwrap();
        image.save("./resources/island.png").unwrap();
    }

    #[test]
    fn test_pcg_tunnels() {
        let tunnels = generate_tunnels(SIZE.into());

        let (size, buffer) = tunnels.into_inner();
        let buffer = buffer
            .into_iter()
            .map(|value| if value { 255 } else { 0 })
            .collect();
        let image = GrayImage::from_vec(size.x as _, size.y as _, buffer).unwrap();
        image.save("./resources/caves.png").unwrap();
    }

    #[test]
    fn test_serde() {
        fn is_serde<T: Serialize + for<'d> Deserialize<'d>>() {}

        is_serde::<Grid<usize>>();
        is_serde::<Grid<f64>>();

        let grid = Grid::new(Vec2::new(10, 10), 0usize);
        let serialized = serde_json::to_string(&grid).unwrap();
        let deserialized: Grid<usize> = serde_json::from_str(&serialized).unwrap();
        assert_eq!(grid.size(), deserialized.size());
        assert_eq!(grid.buffer(), deserialized.buffer());

        let grid = Grid::new(Vec2::new(10, 10), 0.0f64);
        let serialized = serde_json::to_string(&grid).unwrap();
        let deserialized: Grid<f64> = serde_json::from_str(&serialized).unwrap();
        assert_eq!(grid.size(), deserialized.size());
        assert_eq!(grid.buffer(), deserialized.buffer());
    }
}