1mod cli;
2mod config;
3mod error;
4mod model;
5mod loading;
6mod map;
7mod texture;
8mod wave;
9mod metadata;
10
11use std::{
12 collections::HashMap,
13 path::Path,
14 sync::{Arc, Mutex},
15 io::Write
16};
17
18use rayon::prelude::*;
19
20use crate::{
21 config::ModeSettings,
22 map::RegionsMapData,
23 model::game::TitleId,
24 texture::RegionsMapImageBuffer,
25 wave::strategies::{ExpandInitialRegionStrategy, FindNearestNonZeroRegionStrategy}
26};
27
28pub use crate::{
33 cli::Cli,
34 config::Config,
35 error::Error,
36};
37
38const EXPECT_NO_OTHER_ARC_REFERENCES: &str = "no other Arc references";
43
44pub fn run(config: Config) -> Result<(), Error> {
49 let RegionsMapData{
50 image_buffer: mut regions_map_image_buffer,
51 region_title_indices,
52 max_region_index,
53 region_initial_pixel_coords,
54 } = match &config.mode_settings {
55 ModeSettings::TitleBasedRegions { region_title_tier } => {
56 map::build_regions_map_from_mod_data(*region_title_tier, &config.mod_paths)?
57 },
58 ModeSettings::PredefinedRegions { input_regions_map_path } => {
59 map::load_predefined_regions_map(&input_regions_map_path)?
60 },
61 };
62
63 println!("map data loaded successfully");
64
65 if config.must_attempt_early_exit || config.is_dry_run {
66 let is_raw_data_current = is_raw_data_current(
67 &config.mod_paths.raw_regions_map,
68 config.output_metadata_yaml_path.as_ref().map(|p| p.as_path()),
69 ®ions_map_image_buffer,
70 region_title_indices.as_ref()
71 );
72 println!(
73 "existing raw regions map {} title indices are {}",
74 if is_raw_data_current { "and" } else { "and/or" },
75 if is_raw_data_current { "up to date" } else { "stale" }
76 );
77 if is_raw_data_current {
78 return Ok(());
79 } else if config.is_dry_run {
80 println!("will not regenerate due to --dry-run");
81
82 return Ok(());
83 }
84 }
85
86 regions_map_image_buffer.save(config.mod_paths.raw_regions_map.clone())?;
87 println!("succesfully wrote {}", config.mod_paths.raw_regions_map.display());
88
89 let map_dimensions = regions_map_image_buffer.dimensions();
90
91 if config.must_expand_regions {
92 println!("expanding non-zero regions");
95
96 let wave_grid = Arc::new(wave::new_wave_grid(map_dimensions.1 as usize, map_dimensions.0 as usize));
97 let expanded_count = Arc::new(Mutex::new(0));
98 (1..=max_region_index).into_par_iter().for_each(|initial_region_index| {
99 let initial_coords = *region_initial_pixel_coords.get(&initial_region_index)
100 .expect("initial pixel coords must be present for every region");
101
102 wave::expand_wave(&wave_grid, initial_coords, &ExpandInitialRegionStrategy::new(initial_region_index, ®ions_map_image_buffer));
103
104 {
105 let mut count = expanded_count.lock().unwrap();
106 *count += 1;
107 print!("\rexpanded {}/{} regions", *count, max_region_index);
108 std::io::stdout().flush().unwrap();
109 }
110 });
111 let wave_grid = Arc::try_unwrap(wave_grid).expect(EXPECT_NO_OTHER_ARC_REFERENCES);
112
113 println!("\nfilling remaining neutral zones");
114
115 for y in 0..wave_grid.rows() as u32 {
116 for x in 0..wave_grid.cols() as u32 {
117 let coords = (x, y);
118
119 let initial_cell_region_index = wave_grid.get(y as usize, x as usize).unwrap().read().unwrap().region_index;
120 if initial_cell_region_index != 0 {
121 continue;
122 }
123 let nearest_region_index = wave::expand_wave(&wave_grid, coords, &FindNearestNonZeroRegionStrategy::new())
126 .expect("nearest non-zero region must exist");
127
128 wave::set_cell_region_index(&wave_grid, &coords, nearest_region_index);
129 }
130 }
131
132 for y in 0..wave_grid.rows() as u32 {
133 for x in 0..wave_grid.cols() as u32 {
134 let region_index = wave_grid.get(y as usize, x as usize).unwrap().read().unwrap().region_index;
135 if region_index != 0 {
136 let existing_region_index = texture::decode_index_from_rgb(®ions_map_image_buffer.get_pixel(x, y).0[..3]);
137 if existing_region_index == 0 {
139 regions_map_image_buffer.get_pixel_mut(x, y).0 = texture::encode_index_as_rgba(region_index | (0xFF00 << 16));
140 }
141 }
142 }
143 }
144 } else {
145 println!("skipped non-zero regions expansion");
146 }
147
148 regions_map_image_buffer.save(config.mod_paths.regions_map.clone())?;
149 println!("succesfully wrote {}", config.mod_paths.regions_map.display());
150
151 println!("registering adjacencies");
152
153 let mut regions_graph = HashMap::new();
154
155 for y in 0..regions_map_image_buffer.height() {
156 for x in 0..regions_map_image_buffer.width() {
157 let region_title_index = texture::decode_index_from_rgb(®ions_map_image_buffer.get_pixel(x, y).0[..3]);
158
159 for (neighbour_x, neighbour_y) in [(x.checked_sub(1), Some(y)), (Some(x), y.checked_sub(1))] {
161 if neighbour_x.is_none() || neighbour_y.is_none() {
162 continue;
163 }
164
165 let (neighbour_x, neighbour_y) = (neighbour_x.unwrap(), neighbour_y.unwrap());
166
167 regions_map_image_buffer.get_pixel_checked(neighbour_x, neighbour_y)
168 .map(|rgba| {
169 register_adjacency(
170 &mut regions_graph,
171 region_title_index,
172 texture::decode_index_from_rgb(&rgba.0[..3]),
173 (neighbour_x, neighbour_y)
174 );
175 });
176 }
177 }
178 }
179
180 println!("building adjacency and proximity maps");
181
182 let adjacency_map_0_image_buffer = RegionsMapImageBuffer::new(map_dimensions.0, map_dimensions.1);
183 let proximity_map_image_buffer = RegionsMapImageBuffer::new(map_dimensions.0, map_dimensions.1);
184 assert_eq!(adjacency_map_0_image_buffer.dimensions(), map_dimensions);
185 assert_eq!(proximity_map_image_buffer.dimensions(), map_dimensions);
186
187 let adjacency_map_0_image_buffer = Arc::new(Mutex::new(adjacency_map_0_image_buffer));
188 let proximity_map_image_buffer = Arc::new(Mutex::new(proximity_map_image_buffer));
189 let regions_map_image_buffer = Arc::new(regions_map_image_buffer);
190 let regions_graph = Arc::new(regions_graph);
191 let config = Arc::new(config);
192 let processed_rows_count = Arc::new(Mutex::new(0));
193
194 (0..map_dimensions.1).into_par_iter().for_each(|y| {
195 for x in 0..map_dimensions.0 {
196 let current_region_index = texture::decode_index_from_rgb(
197 ®ions_map_image_buffer.get_pixel(x, y).0[..3]
198 );
199 if config.must_expand_regions && current_region_index == 0 {
200 continue;
201 }
202
203 let adjacent_regions: Vec<_> = regions_graph.get(¤t_region_index)
204 .expect("every region must be present in the graph")
205 .0.iter()
206 .map(|(adjacent_region_index, border_pixels_coords)| {
207 let min_sq_distance = border_pixels_coords.iter()
208 .map(|(border_x, border_y)| {
209 (x as f32 - *border_x as f32).powi(2) + (y as f32 - *border_y as f32).powi(2)
210 })
211 .reduce(f32::min)
212 .expect("border pixel set if present cannot be empty");
213
214 (adjacent_region_index, min_sq_distance)
215 })
216 .collect();
217
218 let mut adjacent_regions = adjacent_regions;
219 adjacent_regions.sort_by(|(_, sq_distance_0), (_, sq_distance_1)| {
220 sq_distance_0.partial_cmp(sq_distance_1).expect("all distances must be comparable")
221 });
222
223 const DEFAULT_ADJACENCY_PAIR: &(&u32, f32) = &(&0, f32::INFINITY);
224
225 {
226 let mut adj_buf = adjacency_map_0_image_buffer.lock().unwrap();
227 adj_buf.get_pixel_mut(x, y).0 = texture::encode_index_as_rgba(
228 (adjacent_regions.get(0).unwrap_or(DEFAULT_ADJACENCY_PAIR).0 << 0)
229 | (0xFF00 << 16)
230 );
231 }
232 {
233 let mut prox_buf = proximity_map_image_buffer.lock().unwrap();
234 prox_buf.get_pixel_mut(x, y).0 = sq_distance_to_proximity_rgba(
235 adjacent_regions.get(0).unwrap_or(DEFAULT_ADJACENCY_PAIR).1,
236 config.proximity_distance_limit
237 );
238 }
239 }
240
241 {
242 let mut processed_rows_count = processed_rows_count.lock().unwrap();
243 *processed_rows_count += 1;
244 print!("\rprocessed {}/{} rows", *processed_rows_count, map_dimensions.1);
245 if *processed_rows_count % 128 == 0 {
246 std::io::stdout().flush().unwrap();
247 }
248 }
249 });
250
251 println!(); let adjacency_map_0_image_buffer = Arc::try_unwrap(adjacency_map_0_image_buffer)
254 .expect(EXPECT_NO_OTHER_ARC_REFERENCES)
255 .into_inner()
256 .unwrap();
257 let proximity_map_image_buffer = Arc::try_unwrap(proximity_map_image_buffer)
258 .expect(EXPECT_NO_OTHER_ARC_REFERENCES)
259 .into_inner()
260 .unwrap();
261
262 adjacency_map_0_image_buffer.save(config.mod_paths.adjacency_map_0.clone())?;
263 println!("succesfully wrote {}", config.mod_paths.adjacency_map_0.display());
264 proximity_map_image_buffer.save(config.mod_paths.proximity_map.clone())?;
268 println!("succesfully wrote {}", config.mod_paths.proximity_map.display());
269
270 if config.output_metadata_yaml_path.is_some() {
271 metadata::write_metadata(
272 &config.output_metadata_yaml_path.as_ref().unwrap(),
273 &config,
274 region_title_indices.as_ref(),
275 max_region_index
276 ).map_err(Error::from_write_metadata_error)?;
277 }
278
279 Ok(())
280}
281
282type BorderPixelsSet = Vec<(u32, u32)>;
287
288struct RegionAdjacencies(HashMap<u32, BorderPixelsSet>);
289
290fn is_raw_data_current(
295 raw_regions_map_path: &Path,
296 region_title_indices_yaml_path: Option<&Path>,
297 raw_regions_map_image_buffer: &RegionsMapImageBuffer,
298 region_title_indices: Option<&HashMap<TitleId, u32>>
299) -> bool {
300 if !raw_regions_map_path.is_file() {
301 println!("existing raw regions map not found");
302
303 return false;
304 }
305
306 if !region_title_indices_yaml_path.map_or(false, |path| path.is_file()) {
307 println!("no region title indices YAML file available to check");
308
309 return false;
310 }
311
312 map::is_regions_data_current(raw_regions_map_path, region_title_indices_yaml_path.unwrap(), raw_regions_map_image_buffer, region_title_indices.unwrap())
313}
314
315fn register_adjacency(
316 regions_graph: &mut HashMap<u32, RegionAdjacencies>,
317 current_region_index: u32,
318 adjacent_region_index: u32,
319 border_pixel_coords: (u32, u32)
320) {
321 const BORDER_PIXELS_SET_INITIAL_CAPACITY: usize = 256;
322
323 if current_region_index == adjacent_region_index {
324 return;
325 }
326
327 regions_graph.entry(current_region_index)
330 .or_insert_with(|| RegionAdjacencies(HashMap::new()))
331 .0.entry(adjacent_region_index)
332 .or_insert_with(|| BorderPixelsSet::with_capacity(BORDER_PIXELS_SET_INITIAL_CAPACITY))
333 .push(border_pixel_coords);
334
335 regions_graph.entry(adjacent_region_index)
336 .or_insert_with(|| RegionAdjacencies(HashMap::new()))
337 .0.entry(current_region_index)
338 .or_insert_with(|| BorderPixelsSet::with_capacity(BORDER_PIXELS_SET_INITIAL_CAPACITY))
339 .push(border_pixel_coords);
340}
341
342fn sq_distance_to_proximity_rgba(sq_distance: f32, proximity_distance_limit: f32) -> [u8; 4] {
343 let alpha = (proximity_distance_limit - sq_distance.sqrt()).max(0.0)/proximity_distance_limit;
344 let alpha = (alpha*255.0) as u8;
345
346 [0, 0, 0, alpha]
347}