Skip to main content

terrain_forge/spatial/
morphology.rs

1//! Morphological operations for shape analysis
2
3use crate::{Cell, Grid, Tile};
4
5/// Morphological operations
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum MorphologyOp {
8    /// Erosion - shrink shapes
9    Erosion,
10    /// Dilation - expand shapes  
11    Dilation,
12    /// Opening - erosion followed by dilation
13    Opening,
14    /// Closing - dilation followed by erosion
15    Closing,
16}
17
18/// Structuring element for morphological operations
19#[derive(Debug, Clone)]
20pub struct StructuringElement {
21    pattern: Vec<Vec<bool>>,
22    width: usize,
23    height: usize,
24    center_x: usize,
25    center_y: usize,
26}
27
28impl StructuringElement {
29    /// Create rectangular structuring element
30    pub fn rectangle(width: usize, height: usize) -> Self {
31        Self {
32            pattern: vec![vec![true; width]; height],
33            width,
34            height,
35            center_x: width / 2,
36            center_y: height / 2,
37        }
38    }
39
40    /// Create circular structuring element
41    pub fn circle(radius: usize) -> Self {
42        let size = radius * 2 + 1;
43        let mut pattern = vec![vec![false; size]; size];
44        let center = radius;
45
46        for (y, row) in pattern.iter_mut().enumerate() {
47            for (x, cell) in row.iter_mut().enumerate() {
48                let dx = x as i32 - center as i32;
49                let dy = y as i32 - center as i32;
50                if (dx * dx + dy * dy) as f32 <= (radius * radius) as f32 {
51                    *cell = true;
52                }
53            }
54        }
55
56        Self {
57            pattern,
58            width: size,
59            height: size,
60            center_x: center,
61            center_y: center,
62        }
63    }
64
65    /// Create cross-shaped structuring element
66    pub fn cross(size: usize) -> Self {
67        let mut pattern = vec![vec![false; size]; size];
68        let center = size / 2;
69
70        // Horizontal line
71        for cell in &mut pattern[center] {
72            *cell = true;
73        }
74        // Vertical line
75        for row in &mut pattern {
76            row[center] = true;
77        }
78
79        Self {
80            pattern,
81            width: size,
82            height: size,
83            center_x: center,
84            center_y: center,
85        }
86    }
87
88    pub fn width(&self) -> usize {
89        self.width
90    }
91    pub fn height(&self) -> usize {
92        self.height
93    }
94    pub fn get(&self, x: usize, y: usize) -> bool {
95        self.pattern[y][x]
96    }
97}
98
99/// Apply morphological transformation to grid
100pub fn morphological_transform<C: Cell>(
101    grid: &Grid<C>,
102    op: MorphologyOp,
103    element: &StructuringElement,
104) -> Grid<Tile> {
105    match op {
106        MorphologyOp::Erosion => erosion(grid, element),
107        MorphologyOp::Dilation => dilation(grid, element),
108        MorphologyOp::Opening => {
109            let eroded = erosion(grid, element);
110            dilation_tile(&eroded, element)
111        }
112        MorphologyOp::Closing => {
113            let dilated = dilation(grid, element);
114            erosion_tile(&dilated, element)
115        }
116    }
117}
118
119fn erosion<C: Cell>(grid: &Grid<C>, element: &StructuringElement) -> Grid<Tile> {
120    let mut result = Grid::new(grid.width(), grid.height());
121
122    for y in 0..grid.height() {
123        for x in 0..grid.width() {
124            let mut all_match = true;
125
126            for ey in 0..element.height() {
127                for ex in 0..element.width() {
128                    if !element.get(ex, ey) {
129                        continue;
130                    }
131
132                    let gx = x as i32 + ex as i32 - element.center_x as i32;
133                    let gy = y as i32 + ey as i32 - element.center_y as i32;
134
135                    if let Some(cell) = grid.get(gx, gy) {
136                        if !cell.is_passable() {
137                            all_match = false;
138                            break;
139                        }
140                    } else {
141                        all_match = false;
142                        break;
143                    }
144                }
145                if !all_match {
146                    break;
147                }
148            }
149
150            let tile = if all_match { Tile::Floor } else { Tile::Wall };
151            result.set(x as i32, y as i32, tile);
152        }
153    }
154
155    result
156}
157
158fn dilation<C: Cell>(grid: &Grid<C>, element: &StructuringElement) -> Grid<Tile> {
159    let mut result = Grid::new(grid.width(), grid.height());
160
161    for y in 0..grid.height() {
162        for x in 0..grid.width() {
163            let mut any_match = false;
164
165            for ey in 0..element.height() {
166                for ex in 0..element.width() {
167                    if !element.get(ex, ey) {
168                        continue;
169                    }
170
171                    let gx = x as i32 + ex as i32 - element.center_x as i32;
172                    let gy = y as i32 + ey as i32 - element.center_y as i32;
173
174                    if let Some(cell) = grid.get(gx, gy) {
175                        if cell.is_passable() {
176                            any_match = true;
177                            break;
178                        }
179                    }
180                }
181                if any_match {
182                    break;
183                }
184            }
185
186            let tile = if any_match { Tile::Floor } else { Tile::Wall };
187            result.set(x as i32, y as i32, tile);
188        }
189    }
190
191    result
192}
193
194fn erosion_tile(grid: &Grid<Tile>, element: &StructuringElement) -> Grid<Tile> {
195    erosion(grid, element)
196}
197
198fn dilation_tile(grid: &Grid<Tile>, element: &StructuringElement) -> Grid<Tile> {
199    dilation(grid, element)
200}