1use crate::constants;
2use crate::convert::{To, TryTo};
3use crate::map::*;
4
5use fixed::types::{I17F15, I27F5};
6use image::{ImageError, Pixel, RgbaImage};
7use vek::num_traits::CheckedAdd;
8use vek::Extent2;
9
10use std::fs;
11use std::io;
12use std::io::{BufReader, Read, Seek};
13use std::path::Path;
14
15mod dilate;
16mod extend;
17mod mirror;
18mod optimize_mapres;
19mod rotate;
20mod scale;
21mod shrink;
22mod tiles;
23mod unused;
24
25pub use dilate::dilate;
26pub use extend::edge_extend_ndarray;
27pub use shrink::shrink_ndarray;
28pub use tiles::{
29 DDNetFixPhysicsRotation, DDNetMigrateSpeedup, DDNetNormalizePhysicsRotation, EditTile,
30 TileFlips, ZeroAir, ZeroUnusedParts,
31};
32
33const AMOUNT: u32 = 1150 * 1000;
35const MAX_WIDTH: i32 = 1500;
37const MAX_HEIGHT: i32 = 1150;
39
40pub const MAX_CAMERA_DIMENSIONS: Extent2<I27F5> = Extent2 {
44 w: I27F5::from_bits(MAX_WIDTH),
45 h: I27F5::from_bits(MAX_HEIGHT),
46};
47
48pub fn camera_dimensions(aspect_ratio: f32) -> Extent2<f32> {
52 let mut width = (AMOUNT as f32 * aspect_ratio).sqrt();
61 let mut height = width / aspect_ratio;
63 if width > MAX_WIDTH as f32 {
65 width = MAX_WIDTH as f32;
66 height = width / aspect_ratio;
67 }
68 if height > MAX_HEIGHT as f32 {
69 height = MAX_HEIGHT as f32;
70 width = height * aspect_ratio;
71 }
72 Extent2::new(width / 32., height / 32.)
73}
74
75impl TwMap {
76 pub fn find_physics_layer<T: PhysicsLayer>(&self) -> Option<&T> {
79 match self
80 .physics_group()
81 .layers
82 .iter()
83 .rev()
84 .find(|l| l.kind() == T::kind())
85 {
86 None => None,
87 Some(l) => T::get(l),
88 }
89 }
90
91 pub fn find_physics_layer_mut<T: PhysicsLayer>(&mut self) -> Option<&mut T> {
94 match self
95 .physics_group_mut()
96 .layers
97 .iter_mut()
98 .rev()
99 .find(|l| l.kind() == T::kind())
100 {
101 None => None,
102 Some(l) => T::get_mut(l),
103 }
104 }
105}
106
107impl LayerKind {
108 const fn duplicate_index(&self) -> usize {
110 match self {
111 LayerKind::Game => 0,
112 LayerKind::Front => 1,
113 LayerKind::Switch => 2,
114 LayerKind::Tele => 3,
115 LayerKind::Speedup => 4,
116 LayerKind::Tune => 5,
117 _ => panic!(), }
119 }
120}
121
122impl TwMap {
123 pub fn parse_path_unchecked<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
124 let path = path.as_ref();
125
126 let metadata = fs::metadata(path)?;
127 if metadata.is_file() {
128 let map_data = fs::read(path)?;
129 TwMap::parse(&map_data)
130 } else if metadata.is_dir() {
131 TwMap::parse_dir_unchecked(path)
132 } else {
133 Err(io::Error::new(io::ErrorKind::InvalidData, "Neither a file nor directory").into())
134 }
135 }
136
137 pub fn parse_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
139 let map = TwMap::parse_path_unchecked(&path)?;
140 map.check()?;
141 Ok(map)
142 }
143}
144
145impl Sound {
146 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Sound, opus_headers::ParseError> {
147 let path = path.as_ref();
148 let name = path.file_stem().unwrap().to_str().ok_or_else(|| {
149 io::Error::new(
150 io::ErrorKind::InvalidInput,
151 "The file name includes invalid utf-8",
152 )
153 })?;
154 let reader = std::fs::File::open(path)?;
155 Self::from_reader(name, reader)
156 }
157
158 pub fn from_reader<R: Read>(
160 name: &str,
161 mut reader: R,
162 ) -> Result<Sound, opus_headers::ParseError> {
163 let mut buf = Vec::new();
164 reader.read_to_end(&mut buf)?;
165 opus_headers::parse_from_read(&buf[..])?;
166
167 Ok(Sound {
168 name: name.to_string(),
169 data: buf.into(),
170 })
171 }
172}
173
174impl EmbeddedImage {
175 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<EmbeddedImage, ImageError> {
181 let path = path.as_ref();
182 let name = match path.file_stem().unwrap().to_str() {
183 Some(str) => str,
184 None => {
185 return Err(io::Error::new(
186 io::ErrorKind::InvalidInput,
187 "The file name includes invalid utf-8",
188 )
189 .into())
190 }
191 };
192 let reader = std::fs::File::open(path)?;
193 Self::from_reader(name, reader)
194 }
195
196 pub fn from_reader<R: Read + Seek>(name: &str, inner: R) -> Result<EmbeddedImage, ImageError> {
199 let reader =
200 image::ImageReader::with_format(BufReader::new(inner), image::ImageFormat::Png);
201 let image = reader.decode()?.into_rgba8().into();
202 Ok(EmbeddedImage {
203 name: name.to_string(),
204 image,
205 })
206 }
207}
208
209impl ExternalImage {
210 pub fn embed<P: AsRef<Path>>(&self, mapres_directory: P) -> Result<EmbeddedImage, ImageError> {
212 let mut file_name = self.name.clone();
213 file_name.push_str(".png");
214
215 let path = mapres_directory.as_ref().join(&file_name);
216
217 EmbeddedImage::from_file(path)
218 }
219}
220
221impl TwMap {
222 pub fn embed_images<P: AsRef<Path>>(&mut self, mapres_directory: P) -> Result<(), ImageError> {
224 let mut result = Ok(());
225 for image in &mut self.images {
226 if let Image::External(ex) = image {
227 match ex.embed(mapres_directory.as_ref()) {
228 Ok(emb) => *image = Image::Embedded(emb),
229 Err(err) => {
230 if result.is_ok() {
231 result = Err(err);
232 }
233 }
234 }
235 }
236 }
237 result
238 }
239
240 pub fn embed_images_auto(&mut self) -> Result<(), ImageError> {
243 let mut result = Ok(());
244
245 fn read_mapres(name: &str, version: Version) -> Result<EmbeddedImage, ImageError> {
246 let path = format!("mapres/{name}.png");
247 let file = twstorage::read_file(&path, version.into())?;
248 EmbeddedImage::from_reader(name, file)
249 }
250
251 for image in &mut self.images {
252 if let Image::External(ext) = image {
253 match read_mapres(&ext.name, self.version) {
254 Ok(emb) => *image = Image::Embedded(emb),
255 Err(err) => {
256 if result.is_ok() {
257 result = Err(err);
258 }
259 }
260 }
261 }
262 }
263
264 result
265 }
266}
267
268impl TwMap {
269 pub fn edit_image_indices(&mut self, edit_fn: impl Fn(Option<u16>) -> Option<u16>) {
271 for group in &mut self.groups {
272 for layer in &mut group.layers {
273 match layer {
274 Layer::Tiles(l) => l.image = edit_fn(l.image),
275 Layer::Quads(l) => l.image = edit_fn(l.image),
276 _ => {}
277 }
278 }
279 }
280 }
281
282 pub fn edit_env_indices(&mut self, edit_fn: impl Fn(Option<u16>) -> Option<u16>) {
284 for group in &mut self.groups {
285 for layer in &mut group.layers {
286 use Layer::*;
287 match layer {
288 Tiles(l) => l.color_env = edit_fn(l.color_env),
289 Quads(l) => {
290 for quad in &mut l.quads {
291 quad.color_env = edit_fn(quad.color_env);
292 quad.position_env = edit_fn(quad.position_env);
293 }
294 }
295 Sounds(l) => {
296 for source in &mut l.sources {
297 source.position_env = edit_fn(source.position_env);
298 source.sound_env = edit_fn(source.sound_env);
299 }
300 }
301 _ => {}
302 }
303 }
304 }
305 }
306
307 pub fn edit_sound_indices(&mut self, edit_fn: impl Fn(Option<u16>) -> Option<u16>) {
309 for group in &mut self.groups {
310 for layer in &mut group.layers {
311 if let Layer::Sounds(l) = layer {
312 l.sound = edit_fn(l.sound)
313 }
314 }
315 }
316 }
317}
318
319pub fn calc_opaque_table(image: &RgbaImage) -> [[bool; 16]; 16] {
321 let tile_width = image.width() / 16;
322 let tile_height = image.height() / 16;
323 let mut table = [[false; 16]; 16];
324 if tile_width != tile_height {
325 return table;
326 }
327
328 for tile_y in 0..16 {
329 let tile_y_pos = tile_y * tile_width;
330 for tile_x in 0..16 {
331 let tile_x_pos = tile_x * tile_width;
332 let mut opaque = true;
333
334 'outer: for pixel_y in 0..tile_width {
335 for pixel_x in 0..tile_width {
336 let y = tile_y_pos + pixel_y;
337 let x = tile_x_pos + pixel_x;
338 if image.get_pixel(x, y).channels()[3] < 250 {
339 opaque = false;
340 break 'outer;
341 }
342 }
343 }
344
345 table[tile_y.try_to::<usize>()][tile_x.try_to::<usize>()] = opaque;
346 }
347 }
348 table[0][0] = false; table
350}
351
352impl Image {
353 pub fn calc_opaque_table(&self, version: Version) -> [[bool; 16]; 16] {
355 match self {
356 Image::External(image) => {
357 constants::external_opaque_table(&image.name, version).unwrap_or([[false; 16]; 16])
358 }
359 Image::Embedded(image) => calc_opaque_table(image.image.unwrap_ref()),
360 }
361 }
362}
363
364impl TwMap {
365 pub fn process_tile_flag_opaque(&mut self) {
367 let tables: Vec<[[bool; 16]; 16]> = self
368 .images
369 .iter()
370 .map(|image| image.calc_opaque_table(self.version))
371 .collect();
372 for group in &mut self.groups {
373 for layer in &mut group.layers {
374 if let Layer::Tiles(layer) = layer {
375 let opaque_table = {
376 if layer.color.a != 255 {
377 &[[false; 16]; 16]
378 } else {
379 match layer.image {
380 None => &[[false; 16]; 16],
381 Some(index) => &tables[index.to::<usize>()],
382 }
383 }
384 };
385 let tiles = layer.tiles.unwrap_mut();
386 for tile in tiles {
387 let opaque_value =
388 opaque_table[tile.id.to::<usize>() / 16][tile.id.to::<usize>() % 16];
389 tile.flags.set(TileFlags::OPAQUE, opaque_value);
390 }
391 }
392 }
393 }
394 }
395
396 pub fn set_external_image_dimensions(&mut self) {
398 for image in &mut self.images {
399 if let Image::External(ex) = image {
400 if let Some(size) = constants::external_dimensions(&ex.name, self.version) {
401 ex.size = size;
402 }
403 }
404 }
405 }
406}
407
408impl QuadsLayer {
409 fn shift(&mut self, offset: Vec2<I17F15>) -> Option<()> {
411 for quad in &mut self.quads {
412 quad.position = quad.position.checked_add(&offset)?;
413 for corner in &mut quad.corners {
414 *corner = corner.checked_add(&offset)?;
415 }
416 }
417 Some(())
418 }
419}
420
421impl SoundsLayer {
422 fn shift(&mut self, offset: Vec2<I17F15>) -> Option<()> {
424 for source in &mut self.sources {
425 source
426 .area
427 .set_position(source.area.position().checked_add(&offset)?);
428 }
429 Some(())
430 }
431}
432
433impl TwMap {
434 pub fn isolate_physics_layers(&mut self) {
436 let index = self
437 .groups
438 .iter()
439 .position(|g| g.is_physics_group())
440 .unwrap();
441 let game_group = &mut self.groups[index];
442 let mut front_group = Group {
443 name: String::from("v Front"),
444 ..Group::physics()
445 };
446 let mut back_group = Group {
447 name: String::from("^ Back"),
448 ..Group::physics()
449 };
450 let mut i = 0;
451 let mut after = false;
452 while i < game_group.layers.len() {
453 if !game_group.layers[i].kind().is_physics_layer() {
454 if after {
455 back_group.layers.push(game_group.layers.remove(i));
456 } else {
457 front_group.layers.push(game_group.layers.remove(i));
458 }
459 } else {
460 if game_group.layers[i].kind() == LayerKind::Game {
461 after = true;
462 }
463 i += 1;
464 }
465 }
466 if !back_group.layers.is_empty() {
467 self.groups.insert(index + 1, back_group);
468 }
469 if !front_group.layers.is_empty() {
470 self.groups.insert(index, front_group);
471 }
472 }
473}
474
475fn calc_new_offset(
476 former_offset: I27F5,
477 origin_shift: I27F5,
478 parallax: i32,
479 align_size: I27F5,
480) -> Option<I27F5> {
481 let origin_shift_parallaxed = origin_shift
482 .checked_mul_int(parallax)?
483 .checked_div_int(100)?;
484 let offset_offset = origin_shift_parallaxed.checked_sub(align_size)?;
485 former_offset
486 .checked_add(offset_offset)?
487 .checked_mul_int(-1)
488}
489
490fn calc_new_clip_pos(align_size: I27F5, clip_pos: I27F5, clip_size: I27F5) -> Option<I27F5> {
491 let new_clip_corner_pos = clip_pos.checked_add(clip_size)?;
492 align_size.checked_sub(new_clip_corner_pos)
493}