ck3-regions 0.1.2

Generates title-based region textures for use with the custom dynamic terrain shader system implemented in some CK3 mods.
Documentation
use std::collections::HashSet;

use grid::Grid;

use crate::texture::{self, RegionsMapImageBuffer};

use super::{
    EXPECT_WAVE_GRID_BOUNDS,
    WaveGrid,
    VisitedCoordsTracker, WaveCellData,
};

//
// pub enum WaveCellEvaluation
//

pub enum WaveCellResponse<O> {
    ProceedWithDepth(u32),
    SkipNeighbours,
    ReturnOutput(O)
}

//
// pub trait WaveStrategy
//

pub trait WaveStrategy {
    type Output;
    type VisitedCoords: VisitedCoordsTracker;

    fn make_visited_coords_tracker(wave_grid_rows: usize, wave_grid_cols: usize) -> Self::VisitedCoords;

    fn get_region_index_at_coords(&self, wave_grid: &WaveGrid, coords: &(u32, u32)) -> u32 {
        wave_grid.get(coords.1 as usize, coords.0 as usize)
            .expect(EXPECT_WAVE_GRID_BOUNDS)
            .read()
            .expect("wave grid cell must be readable")
            .region_index
    }

    fn process_cell(&self, _cell_data: &mut WaveCellData, expansion_depth: u32, _region_index_at_coords: u32) -> WaveCellResponse<Self::Output> {
        WaveCellResponse::ProceedWithDepth(expansion_depth + 1)
    }
}

//
// pub struct ExpandInitialRegionStrategy
//

pub struct ExpandInitialRegionStrategy<'a> {
    initial_region_index:     u32,
    regions_map_image_buffer: &'a RegionsMapImageBuffer
}

impl<'a> WaveStrategy for ExpandInitialRegionStrategy<'a> {
    type Output        = ();
    type VisitedCoords = Grid<bool>;

    fn make_visited_coords_tracker(wave_grid_rows: usize, wave_grid_cols: usize) -> Self::VisitedCoords {
        Self::VisitedCoords::new(wave_grid_rows, wave_grid_cols)
    }

    fn get_region_index_at_coords(&self, wave_grid: &WaveGrid, coords: &(u32, u32)) -> u32 {
        assert_eq!((wave_grid.cols() as u32, wave_grid.rows() as u32), self.regions_map_image_buffer.dimensions());

        let pixel_at_coords = self.regions_map_image_buffer.get_pixel(coords.0, coords.1);

        texture::decode_index_from_rgb(&pixel_at_coords.0[..3])
    }

    fn process_cell(&self, cell_data: &mut WaveCellData, expansion_depth: u32, region_index_at_coords: u32) -> WaveCellResponse<Self::Output> {
        if region_index_at_coords != 0 && region_index_at_coords != self.initial_region_index {
            return WaveCellResponse::SkipNeighbours;
        }

        let new_depth = expansion_depth + if region_index_at_coords == 0 { 1 } else { 0 };

        if cell_data.region_index == 0 || cell_data.depth > new_depth {
            cell_data.region_index = self.initial_region_index;
            cell_data.depth        = new_depth;

            WaveCellResponse::ProceedWithDepth(new_depth)
        } else {
            WaveCellResponse::SkipNeighbours
        }
    }
}

impl<'a> ExpandInitialRegionStrategy<'a> {
    pub fn new(initial_region_index: u32, regions_map_image_buffer: &'a RegionsMapImageBuffer) -> Self {
        Self{initial_region_index, regions_map_image_buffer}
    }
}

//
// pub struct FillWithNearestRegionStrategy
//

#[derive(Default, Copy, Clone)]
pub struct FindNearestNonZeroRegionStrategy;

impl WaveStrategy for FindNearestNonZeroRegionStrategy {
    type Output        = u32;
    type VisitedCoords = HashSet<(u32, u32)>;

    fn make_visited_coords_tracker(_wave_grid_rows: usize, _wave_grid_cols: usize) -> Self::VisitedCoords {
        Self::VisitedCoords::new()
    }

    fn process_cell(&self, cell_data: &mut WaveCellData, expansion_depth: u32, region_index_at_coords: u32) -> WaveCellResponse<Self::Output> {
        assert_eq!(cell_data.region_index, region_index_at_coords);

        if cell_data.region_index == 0 {
            WaveCellResponse::ProceedWithDepth(expansion_depth + 1)
        } else {
            WaveCellResponse::ReturnOutput(cell_data.region_index)
        }
    }
}

impl FindNearestNonZeroRegionStrategy {
    pub fn new() -> Self {
        Self::default()
    }
}