terrain_forge/spatial/
morphology.rs1use crate::{Cell, Grid, Tile};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum MorphologyOp {
8 Erosion,
10 Dilation,
12 Opening,
14 Closing,
16}
17
18#[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 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 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 pub fn cross(size: usize) -> Self {
67 let mut pattern = vec![vec![false; size]; size];
68 let center = size / 2;
69
70 for cell in &mut pattern[center] {
72 *cell = true;
73 }
74 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
99pub 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}