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
pub mod strategies;

mod trackers;

use std::{
    collections::BinaryHeap,
    cmp::Ordering,
    sync::RwLock
};

use grid::Grid;

use self::trackers::VisitedCoordsTracker;

//
// Exports
//

pub use self::strategies::WaveStrategy;

//
// Constants
//

const EXPECT_WAVE_GRID_BOUNDS: &str = "expected wave grid coords to be in bounds";

//
// Interface aliases
//

pub type WaveGrid = Grid<RwLock<WaveCellData>>;

//
// pub struct WaveCellData
//

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct WaveCellData {
    pub region_index: u32,
    depth:            u32
}

impl Default for WaveCellData {
    fn default() -> Self {
        Self { region_index: 0, depth: u32::MAX }
    }
}

//
// struct WaveCellLink
//

#[derive(Clone, Default, PartialEq, Eq)]
struct WaveCellLink {
    coords: (u32, u32),
    depth:  u32
}

impl Ord for WaveCellLink {
    fn cmp(&self, other: &Self) -> Ordering {
        self.depth.cmp(&other.depth)
            .reverse() // For BinaryHeap<WaveCell> to be a min-heap
    }
}

impl PartialOrd for WaveCellLink {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.depth.partial_cmp(&other.depth)
            .map(Ordering::reverse) // For consistency with Ord
    }
}

//
// Interface
//

pub fn set_cell_region_index(wave_grid: &WaveGrid, coords: &(u32, u32), region_index: u32) {
    wave_grid.get(coords.1 as usize, coords.0 as usize)
        .expect(EXPECT_WAVE_GRID_BOUNDS)
        .write()
        .expect("wave grid cell must be writable")
        .region_index = region_index;
}

pub fn expand_wave<S>(wave_grid: &WaveGrid, initial_coords: (u32, u32), strategy: &S) -> Option<S::Output>
    where S: WaveStrategy
{
    let mut wave_front     = BinaryHeap::new();
    let mut visited_coords = S::make_visited_coords_tracker(wave_grid.rows(), wave_grid.cols());

    let initial_cell_link = WaveCellLink{
        coords: initial_coords,
        depth:  0,
    };

    wave_front.push(initial_cell_link);

    while let Some(cell_link) = wave_front.pop() {
        visited_coords.mark_visited_coords(cell_link.coords);

        let region_index_at_coords = strategy.get_region_index_at_coords(wave_grid, &cell_link.coords);
        let mut current_cell_data  = wave_grid.get(cell_link.coords.1 as usize, cell_link.coords.0 as usize)
            .expect(EXPECT_WAVE_GRID_BOUNDS)
            .write()
            .expect("wave grid cell must be writable");

        let cell_response = strategy.process_cell(&mut current_cell_data, cell_link.depth, region_index_at_coords);
        match cell_response {
            strategies::WaveCellResponse::SkipNeighbours => {
                continue;
            },
            strategies::WaveCellResponse::ReturnOutput(output) => {
                return Some(output);
            },
            strategies::WaveCellResponse::ProceedWithDepth(new_depth) => {
                if cell_link.coords.0 > 0 && !visited_coords.has_visited_coords(&(cell_link.coords.0 - 1, cell_link.coords.1)) {
                    wave_front.push(WaveCellLink { coords: (cell_link.coords.0 - 1, cell_link.coords.1), depth: new_depth });
                }

                if cell_link.coords.1 > 0 && !visited_coords.has_visited_coords(&(cell_link.coords.0, cell_link.coords.1 - 1)) {
                    wave_front.push(WaveCellLink { coords: (cell_link.coords.0, cell_link.coords.1 - 1), depth: new_depth });
                }

                if cell_link.coords.0 < (wave_grid.cols() as u32) - 1 && !visited_coords.has_visited_coords(&(cell_link.coords.0 + 1, cell_link.coords.1)) {
                    wave_front.push(WaveCellLink { coords: (cell_link.coords.0 + 1, cell_link.coords.1), depth: new_depth });
                }

                if cell_link.coords.1 < (wave_grid.rows() as u32) - 1 && !visited_coords.has_visited_coords(&(cell_link.coords.0, cell_link.coords.1 + 1)) {
                    wave_front.push(WaveCellLink { coords: (cell_link.coords.0, cell_link.coords.1 + 1), depth: new_depth });
                }
            }
        };
    }

    None
}

pub fn new_wave_grid(rows: usize, cols: usize) -> WaveGrid {
    let mut grid = Grid::new(rows, cols);
    grid.fill_with(|| RwLock::new(WaveCellData::default()));
    grid
}