mindustry logic execution, map- and schematic- parsing and rendering
Implement schematic resizing
KosmosPrime 2023-02-09
parent 49bcd2a · commit 8a5f4ab
-rw-r--r--src/data/schematic.rs104
-rw-r--r--src/exe/edit.rs113
2 files changed, 215 insertions, 2 deletions
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index a073eff..c0ae97d 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -429,6 +429,56 @@ impl<'l> Schematic<'l>
}
}
+ pub fn resize(&mut self, dx: i16, dy: i16, w: u16, h: u16) -> Result<(), ResizeError>
+ {
+ if w > MAX_DIMENSION
+ {
+ return Err(ResizeError::TargetWidth(w));
+ }
+ if h > MAX_DIMENSION
+ {
+ return Err(ResizeError::TargetHeight(h));
+ }
+ if dx <= -(w as i16) || dx >= self.width as i16
+ {
+ return Err(ResizeError::XOffset{dx, old_w: self.width, new_w: w});
+ }
+ if dy <= -(h as i16) || dy >= self.height as i16
+ {
+ return Err(ResizeError::YOffset{dy, old_h: self.height, new_h: h});
+ }
+ // check that all blocks fit into the new bounds
+ let mut right = 0u16;
+ let mut top = 0u16;
+ let mut left = 0u16;
+ let mut bottom = 0u16;
+ let right_bound = dx + w as i16 - 1;
+ let top_bound = dy + h as i16 - 1;
+ let left_bound = dx;
+ let bottom_bound = dy;
+ for Placement{pos, block, ..} in self.blocks.iter()
+ {
+ let sz = block.get_size() as u16;
+ let (x0, y0, x1, y1) = (pos.0 - (sz - 1) / 2, pos.1 - (sz - 1) / 2, pos.0 + sz / 2, pos.1 + sz / 2);
+ if (x1 as i16) > right_bound && x1 - right_bound as u16 > right {right = x1 - right_bound as u16;}
+ if (y1 as i16) > top_bound && y1 - top_bound as u16 > top {top = y1 - top_bound as u16;}
+ if (x0 as i16) < left_bound && left_bound as u16 - x0 > left {left = left_bound as u16 - x0;}
+ if (y0 as i16) < bottom_bound && bottom_bound as u16 - y0 > bottom {bottom = bottom_bound as u16 - y0;}
+ }
+ if left > 0 || top > 0 || left > 0 || bottom > 0
+ {
+ return Err(ResizeError::Truncated{right, top, left, bottom});
+ }
+ self.width = w;
+ self.height = h;
+ for Placement{pos, ..} in self.blocks.iter_mut()
+ {
+ pos.0 = (pos.0 as i16 + dx) as u16;
+ pos.1 = (pos.1 as i16 + dy) as u16;
+ }
+ Ok(())
+ }
+
pub fn rotate_180(&mut self)
{
self.mirror(true, true);
@@ -541,6 +591,60 @@ impl Error for PlaceError
}
}
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ResizeError
+{
+ TargetWidth(u16),
+ TargetHeight(u16),
+ XOffset{dx: i16, old_w: u16, new_w: u16},
+ YOffset{dy: i16, old_h: u16, new_h: u16},
+ Truncated{right: u16, top: u16, left: u16, bottom: u16},
+}
+
+impl fmt::Display for ResizeError
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
+ {
+ match self
+ {
+ Self::TargetWidth(w) => write!(f, "invalid target width ({w})"),
+ Self::TargetHeight(w) => write!(f, "invalid target height ({w})"),
+ Self::XOffset{dx, old_w, new_w} => write!(f, "horizontal offset {dx} not in [-{new_w}, {old_w}]"),
+ Self::YOffset{dy, old_h, new_h} => write!(f, "vertical offset {dy} not in [-{new_h}, {old_h}]"),
+ Self::Truncated{right, top, left, bottom} =>
+ {
+ macro_rules!fmt_dir
+ {
+ ($f:ident, $first:ident, $name:expr, $value:expr) =>
+ {
+ if $value != 0
+ {
+ if $first
+ {
+ f.write_str(" (")?;
+ $first = false;
+ }
+ else {f.write_str(", ")?;}
+ write!(f, "{}: {}", $name, $value)?;
+ }
+ };
+ }
+
+ f.write_str("truncated blocks")?;
+ let mut first = true;
+ fmt_dir!(f, first, "right", *right);
+ fmt_dir!(f, first, "top", *top);
+ fmt_dir!(f, first, "left", *left);
+ fmt_dir!(f, first, "bottom", *bottom);
+ if !first {f.write_char(')')?;}
+ Ok(())
+ },
+ }
+ }
+}
+
+impl Error for ResizeError {}
+
impl<'l> fmt::Display for Schematic<'l>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
diff --git a/src/exe/edit.rs b/src/exe/edit.rs
index 051394b..d24108f 100644
--- a/src/exe/edit.rs
+++ b/src/exe/edit.rs
@@ -6,7 +6,7 @@ use std::fs;
use crate::block::{BlockRegistry, build_registry, Rotation};
use crate::data::dynamic::DynData;
use crate::data::{DataRead, Serializer, DataWrite, base64};
-use crate::data::schematic::{Schematic, SchematicSerializer};
+use crate::data::schematic::{ResizeError, Schematic, SchematicSerializer};
use crate::exe::print::print_schematic;
use crate::exe::print_err;
use crate::exe::args::{self, ArgCount, ArgOption, OptionHandler};
@@ -227,7 +227,7 @@ impl<'l> Tokenizer<'l>
enum Command
{
- Help, New, Input, Load, Place, Rotate, Mirror, Remove, Print, Dump, Save, Quit
+ Help, New, Input, Load, Place, Rotate, Mirror, Move, Resize, Remove, Print, Dump, Save, Quit
}
impl Command
@@ -243,6 +243,8 @@ impl Command
Self::Place => println!("{:<indent$}Places a block if enough space is available", "\"place\":"),
Self::Rotate => println!("{:<indent$}Rotates the schematic (CCW) in increments of 90 degrees", "\"rotate\":"),
Self::Mirror => println!("{:<indent$}Mirrors the schematic horizontally or vertically", "\"mirror\":"),
+ Self::Move => println!("{:<indent$}Moves all blocks by a certain offset", "\"move\":"),
+ Self::Resize => println!("{:<indent$}Resizes the schematic and offsets it", "\"resize\":"),
Self::Remove => println!("{:<indent$}Removes blocks at a position or within a region", "\"remove\":"),
Self::Print => println!("{:<indent$}Prints the schematic in a visual representation", "\"print\":"),
Self::Dump => println!("{:<indent$}Prints the schematic as a base-64 encoded string", "\"dump\":"),
@@ -267,6 +269,8 @@ impl Command
},
Self::Rotate => println!(r#"{:indent$} Usage: "rotate" <angle>"#, ""),
Self::Mirror => println!(r#"{:indent$} Usage: "mirror" <axis>"#, ""),
+ Self::Move => println!(r#"{:indent$} Usage: "move" <dx> <dy>"#, ""),
+ Self::Resize => println!(r#"{:indent$} Usage: "resize" <width> <height> [<dx> <dy>]"#, ""),
Self::Remove => println!(r#"{:indent$} Usage: "remove" <x0> <y0> [<x1> <y1>]"#, ""),
Self::Print | Self::Dump => (),
Self::Save => println!(r#"{:indent$} Usage: "save" <save path>"#, ""),
@@ -598,6 +602,111 @@ fn interpret(state: &mut State, cmd: &str)
schematic.mirror(x, y);
state.unsaved = true;
},
+ Some("move") =>
+ {
+ let Some(ref mut schematic) = state.schematic
+ else
+ {
+ eprintln!(r#"Command "move" requires an active schematic (see "help")"#);
+ return;
+ };
+ let dx = parse_num!(Move, tokens, "dx", i16);
+ let dy = parse_num!(Move, tokens, "dy", i16);
+ if tokens.remainder().is_some()
+ {
+ eprintln!(r#"Too many parameters for "move""#);
+ Command::Move.print_usage(0);
+ return;
+ }
+ if dx != 0 && dy != 0
+ {
+ if let Err(e) = schematic.resize(dx, dy, schematic.get_width(), schematic.get_height())
+ {
+ match e
+ {
+ ResizeError::XOffset{dx, old_w, new_w} =>
+ {
+ debug_assert_eq!(old_w, new_w);
+ eprintln!("Invalid horizontal move {dx} not in ]-{old_w}, {new_w}[");
+ },
+ ResizeError::YOffset{dy, old_h, new_h} =>
+ {
+ debug_assert_eq!(old_h, new_h);
+ eprintln!("Invalid vertical move {dy} not in ]-{old_h}, {new_h}[");
+ },
+ ResizeError::Truncated{right, top, left, bottom} =>
+ {
+ eprint!("Move would truncate schematic: ");
+ let mut first = true;
+ if right > 0
+ {
+ eprint!("{}right: {right}", if first {""} else {", "});
+ first = false;
+ }
+ if top > 0
+ {
+ eprint!("{}top: {top}", if first {""} else {", "});
+ first = false;
+ }
+ if left > 0
+ {
+ eprint!("{}left: {left}", if first {""} else {", "});
+ first = false;
+ }
+ if bottom > 0
+ {
+ eprint!("{}bottom: {bottom}", if first {""} else {", "});
+ first = false;
+ }
+ if first {eprintln!("<unknown>");} else {eprintln!();}
+ },
+ _ => print_err!(e, "Unexpected resize error (for {dx} / {dy})")
+ }
+ }
+ else {state.unsaved = true;}
+ }
+ },
+ Some("resize") =>
+ {
+ let Some(ref mut schematic) = state.schematic
+ else
+ {
+ eprintln!(r#"Command "resize" requires an active schematic (see "help")"#);
+ return;
+ };
+ let w = parse_num!(Resize, tokens, "width", u16);
+ if w == 0
+ {
+ eprintln!("Schematic width must be positive");
+ return;
+ }
+ let h = parse_num!(Resize, tokens, "height", u16);
+ if h == 0
+ {
+ eprintln!("Schematic height must be positive");
+ return;
+ }
+ let (dx, dy) = if let arg @ Some(..) = tokens.next()
+ {
+ let dx = parse_num!(Resize, "dx", <i16>::from(arg));
+ let dy = parse_num!(Resize, tokens, "dy", i16);
+ (dx, dy)
+ }
+ else {(0, 0)};
+ if tokens.remainder().is_some()
+ {
+ eprintln!(r#"Too many parameters for "resize""#);
+ Command::Resize.print_usage(0);
+ return;
+ }
+ if w != schematic.get_width() || h != schematic.get_height() || dx != 0 || dy != 0
+ {
+ if let Err(e) = schematic.resize(dx, dy, w, h)
+ {
+ print_err!(e, "Could not resize schematic");
+ }
+ }
+ },
Some("remove") =>
{
let Some(ref mut schematic) = state.schematic