mindustry logic execution, map- and schematic- parsing and rendering
91 files changed, 1491 insertions, 1515 deletions
diff --git a/.github/example.png b/.github/example.png Binary files differindex 26431d9..af8ce7d 100644 --- a/.github/example.png +++ b/.github/example.png @@ -22,11 +22,12 @@ const-str = "0.5.5" color-hex = "0.2.0" zip = { version = "0.6.6", features = ["zstd"], default-features = false } tinyrand = "0.5.0" -seq-macro = "0.3.4" tinyrand-std = "0.5.0" dashmap = "5.4.0" fast_image_resize = "2.7.3" thiserror = "1.0.41" +bobbin-bits = "0.1.1" +blurslice = "0.1.0" [build-dependencies] zip = { version = "0.6.6", features = ["zstd"], default-features = false } @@ -41,3 +42,6 @@ debug = 2 opt-level = 3 lto = true incremental = true + +[dev-dependencies] +diff = "0.1" diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-0-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-0-1.png Binary files differdeleted file mode 100644 index 4e3d884..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-0-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-0-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-0-2.png Binary files differdeleted file mode 100644 index ca1ca5e..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-0-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-0-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-0-3.png Binary files differdeleted file mode 100644 index b5afa99..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-0-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-0-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-0.png Binary files differindex 2995011..2995011 100644 --- a/assets/blocks/distribution/conveyors/armored-conveyor-0-0.png +++ b/assets/blocks/distribution/conveyors/armored-conveyor-0.png diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-1-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-1-1.png Binary files differdeleted file mode 100644 index b951ec1..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-1-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-1-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-1-2.png Binary files differdeleted file mode 100644 index db58abe..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-1-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-1-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-1-3.png Binary files differdeleted file mode 100644 index 8e8ecae..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-1-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-1-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-1.png Binary files differindex 08def6a..08def6a 100644 --- a/assets/blocks/distribution/conveyors/armored-conveyor-1-0.png +++ b/assets/blocks/distribution/conveyors/armored-conveyor-1.png diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-2-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-2-1.png Binary files differdeleted file mode 100644 index c1c7395..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-2-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-2-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-2-2.png Binary files differdeleted file mode 100644 index 5401d76..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-2-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-2-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-2-3.png Binary files differdeleted file mode 100644 index feb39f4..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-2-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-2-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-2.png Binary files differindex dde9319..dde9319 100644 --- a/assets/blocks/distribution/conveyors/armored-conveyor-2-0.png +++ b/assets/blocks/distribution/conveyors/armored-conveyor-2.png diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-3-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-3-1.png Binary files differdeleted file mode 100644 index 722ba2d..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-3-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-3-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-3-2.png Binary files differdeleted file mode 100644 index 8f3b6a0..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-3-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-3-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-3-3.png Binary files differdeleted file mode 100644 index ec4e840..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-3-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-3-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-3.png Binary files differindex a4e76f2..a4e76f2 100644 --- a/assets/blocks/distribution/conveyors/armored-conveyor-3-0.png +++ b/assets/blocks/distribution/conveyors/armored-conveyor-3.png diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-4-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-4-1.png Binary files differdeleted file mode 100644 index 2d7f090..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-4-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-4-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-4-2.png Binary files differdeleted file mode 100644 index a1d8c76..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-4-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-4-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-4-3.png Binary files differdeleted file mode 100644 index e707dd8..0000000 --- a/assets/blocks/distribution/conveyors/armored-conveyor-4-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-4-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-4.png Binary files differindex 868b0f4..868b0f4 100644 --- a/assets/blocks/distribution/conveyors/armored-conveyor-4-0.png +++ b/assets/blocks/distribution/conveyors/armored-conveyor-4.png diff --git a/assets/blocks/distribution/conveyors/conveyor-0-1.png b/assets/blocks/distribution/conveyors/conveyor-0-1.png Binary files differdeleted file mode 100644 index 6f0b92f..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-0-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-0-2.png b/assets/blocks/distribution/conveyors/conveyor-0-2.png Binary files differdeleted file mode 100644 index 2633e5d..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-0-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-0-3.png b/assets/blocks/distribution/conveyors/conveyor-0-3.png Binary files differdeleted file mode 100644 index 4406e12..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-0-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-0-0.png b/assets/blocks/distribution/conveyors/conveyor-0.png Binary files differindex 5f7264e..5f7264e 100644 --- a/assets/blocks/distribution/conveyors/conveyor-0-0.png +++ b/assets/blocks/distribution/conveyors/conveyor-0.png diff --git a/assets/blocks/distribution/conveyors/conveyor-1-1.png b/assets/blocks/distribution/conveyors/conveyor-1-1.png Binary files differdeleted file mode 100644 index 6dfe694..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-1-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-1-2.png b/assets/blocks/distribution/conveyors/conveyor-1-2.png Binary files differdeleted file mode 100644 index ef313ad..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-1-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-1-3.png b/assets/blocks/distribution/conveyors/conveyor-1-3.png Binary files differdeleted file mode 100644 index e5ccf27..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-1-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-1-0.png b/assets/blocks/distribution/conveyors/conveyor-1.png Binary files differindex c50b281..c50b281 100644 --- a/assets/blocks/distribution/conveyors/conveyor-1-0.png +++ b/assets/blocks/distribution/conveyors/conveyor-1.png diff --git a/assets/blocks/distribution/conveyors/conveyor-2-1.png b/assets/blocks/distribution/conveyors/conveyor-2-1.png Binary files differdeleted file mode 100644 index 24f5127..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-2-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-2-2.png b/assets/blocks/distribution/conveyors/conveyor-2-2.png Binary files differdeleted file mode 100644 index 8faeeaa..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-2-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-2-3.png b/assets/blocks/distribution/conveyors/conveyor-2-3.png Binary files differdeleted file mode 100644 index 1fa6d17..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-2-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-2-0.png b/assets/blocks/distribution/conveyors/conveyor-2.png Binary files differindex 9dc27e1..9dc27e1 100644 --- a/assets/blocks/distribution/conveyors/conveyor-2-0.png +++ b/assets/blocks/distribution/conveyors/conveyor-2.png diff --git a/assets/blocks/distribution/conveyors/conveyor-3-1.png b/assets/blocks/distribution/conveyors/conveyor-3-1.png Binary files differdeleted file mode 100644 index bd7f0b5..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-3-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-3-2.png b/assets/blocks/distribution/conveyors/conveyor-3-2.png Binary files differdeleted file mode 100644 index b8bd971..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-3-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-3-3.png b/assets/blocks/distribution/conveyors/conveyor-3-3.png Binary files differdeleted file mode 100644 index 4870394..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-3-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-3-0.png b/assets/blocks/distribution/conveyors/conveyor-3.png Binary files differindex 0b92a66..0b92a66 100644 --- a/assets/blocks/distribution/conveyors/conveyor-3-0.png +++ b/assets/blocks/distribution/conveyors/conveyor-3.png diff --git a/assets/blocks/distribution/conveyors/conveyor-4-1.png b/assets/blocks/distribution/conveyors/conveyor-4-1.png Binary files differdeleted file mode 100644 index 74be7c8..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-4-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-4-2.png b/assets/blocks/distribution/conveyors/conveyor-4-2.png Binary files differdeleted file mode 100644 index 92fb21e..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-4-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-4-3.png b/assets/blocks/distribution/conveyors/conveyor-4-3.png Binary files differdeleted file mode 100644 index 9124688..0000000 --- a/assets/blocks/distribution/conveyors/conveyor-4-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/conveyor-4-0.png b/assets/blocks/distribution/conveyors/conveyor-4.png Binary files differindex adf781a..adf781a 100644 --- a/assets/blocks/distribution/conveyors/conveyor-4-0.png +++ b/assets/blocks/distribution/conveyors/conveyor-4.png diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-0-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-0-1.png Binary files differdeleted file mode 100644 index be18171..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-0-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-0-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-0-2.png Binary files differdeleted file mode 100644 index 020968c..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-0-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-0-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-0-3.png Binary files differdeleted file mode 100644 index 5756c57..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-0-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-0-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-0.png Binary files differindex 7cd0f41..7cd0f41 100644 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-0-0.png +++ b/assets/blocks/distribution/conveyors/titanium-conveyor-0.png diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-1-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-1-1.png Binary files differdeleted file mode 100644 index 89ad916..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-1-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-1-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-1-2.png Binary files differdeleted file mode 100644 index fa082af..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-1-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-1-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-1-3.png Binary files differdeleted file mode 100644 index fd22fd4..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-1-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-1-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-1.png Binary files differindex 0f8b737..0f8b737 100644 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-1-0.png +++ b/assets/blocks/distribution/conveyors/titanium-conveyor-1.png diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-2-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-2-1.png Binary files differdeleted file mode 100644 index a53a5c8..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-2-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-2-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-2-2.png Binary files differdeleted file mode 100644 index 758743d..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-2-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-2-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-2-3.png Binary files differdeleted file mode 100644 index 4da1385..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-2-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-2-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-2.png Binary files differindex a0f2ef7..a0f2ef7 100644 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-2-0.png +++ b/assets/blocks/distribution/conveyors/titanium-conveyor-2.png diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-3-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-3-1.png Binary files differdeleted file mode 100644 index cb75fba..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-3-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-3-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-3-2.png Binary files differdeleted file mode 100644 index 9a07028..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-3-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-3-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-3-3.png Binary files differdeleted file mode 100644 index 40d9b6e..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-3-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-3-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-3.png Binary files differindex cdaa149..cdaa149 100644 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-3-0.png +++ b/assets/blocks/distribution/conveyors/titanium-conveyor-3.png diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-4-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-4-1.png Binary files differdeleted file mode 100644 index 939dd83..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-4-1.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-4-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-4-2.png Binary files differdeleted file mode 100644 index 571fbd6..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-4-2.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-4-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-4-3.png Binary files differdeleted file mode 100644 index be9095b..0000000 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-4-3.png +++ /dev/null diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-4-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-4.png Binary files differindex b8fbdcf..b8fbdcf 100644 --- a/assets/blocks/distribution/conveyors/titanium-conveyor-4-0.png +++ b/assets/blocks/distribution/conveyors/titanium-conveyor-4.png @@ -11,15 +11,19 @@ fn zip_dir<T>( it: &mut dyn Iterator<Item = DirEntry>, prefix: &str, writer: T, - method: zip::CompressionMethod, ) -> zip::result::ZipResult<()> where T: Write + Seek, { let mut zip = zip::ZipWriter::new(writer); - let options = FileOptions::default() - .compression_method(method) + let mut options = FileOptions::default() + .compression_method(zip::CompressionMethod::Zstd) .unix_permissions(0o755); + if let Ok(v) = std::env::var("COMPRESS") { + if v == "1" { + options = options.compression_level(Some(22)); + } + } let mut buffer = Vec::new(); for entry in it { @@ -59,6 +63,5 @@ fn main() -> Result<(), ZipError> { &mut it.filter_map(|e| e.ok()), "assets", File::create(std::env::var("OUT_DIR").unwrap() + "/asset").unwrap(), - zip::CompressionMethod::Zstd, ) } diff --git a/src/access.rs b/src/access.rs index ad23b51..561d82c 100644 --- a/src/access.rs +++ b/src/access.rs @@ -3,6 +3,7 @@ use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt; use std::hash::{Hash, Hasher}; + use std::ops::Deref; pub type BoxAccess<'a, D> = Access<'a, Box<D>, D>; diff --git a/src/block/distribution.rs b/src/block/distribution.rs index 69db26b..9a80e45 100644 --- a/src/block/distribution.rs +++ b/src/block/distribution.rs @@ -1,19 +1,269 @@ //! conveyors ( & ducts ) -use std::error::Error; -use std::fmt; +use std::borrow::BorrowMut; use crate::block::simple::*; use crate::block::*; use crate::content; use crate::data::dynamic::DynType; -use crate::data::renderer::{load, ImageHolder}; use crate::item; -use crate::utils::ImageUtils; +use bobbin_bits::U4; +use image::imageops::{flip_horizontal_in_place as flip_h, flip_vertical_in_place as flip_v}; +#[cfg(test)] +macro_rules! dir { + (^) => { + crate::block::Rotation::Up + }; + (v) => { + crate::block::Rotation::Down + }; + (<) => { + crate::block::Rotation::Left + }; + (>) => { + crate::block::Rotation::Right + }; +} +#[cfg(test)] +macro_rules! conv { + (_) => { + None + }; + ($dir:tt) => { + Some(( + &crate::block::distribution::CONVEYOR, + crate::block::distribution::dir!($dir), + )) + }; +} +#[cfg(test)] +macro_rules! define { + ($a:tt,$b:tt,$c:tt,$d:tt) => { + [ + crate::block::distribution::conv!($a), + crate::block::distribution::conv!($b), + crate::block::distribution::conv!($c), + crate::block::distribution::conv!($d), + ] + }; +} +#[cfg(test)] +pub(crate) use conv; +#[cfg(test)] +pub(crate) use define; +#[cfg(test)] +pub(crate) use dir; + +#[test] +fn test_mask() { + macro_rules! assert { + ($a:tt,$b:tt,$c:tt,$d:tt => $rot: tt => $expect: expr) => { + assert_eq!(mask!(define!($a, $b, $c, $d), $rot), $expect) + }; + } + macro_rules! mask { + ($cross:expr, $rot: tt) => { + mask( + &RenderingContext { + position: PositionContext { + position: GridPos(5, 5), + width: 10, + height: 10, + }, + cross: $cross, + rotation: dir!($rot), + }, + "conveyor", + ) + }; + } + assert!(_,_,_,_ => ^ => U4::B0000); + assert!(v,_,_,_ => > => U4::B1000); + assert!(v,v,_,_ => v => U4::B1000); + assert!(_,v,>,_ => > => U4::B0000); + assert!(v,>,<,> => ^ => U4::B0001); + assert!(v,>,>,_ => > => U4::B1000); +} -make_simple!(ConveyorBlock); +fn mask(ctx: &RenderingContext, n: &str) -> U4 { + macro_rules! c { + ($in: expr, $srot: expr, $name: expr, $at: expr) => {{ + if let Some((b, rot)) = $in { + if b.name() == $name { + // if they go down, we must not go up + (rot == $at && rot.mirrored(true, true) != $srot) as u8 + } else { + 0 + } + } else { + 0 + } + }}; + } + use Rotation::*; + let mut x = 0b0000; + + // println!("{:?}, {ctx}", ctx.cross); + x |= 8 * c!(ctx.cross[0], ctx.rotation, n, Down); + x |= 4 * c!(ctx.cross[1], ctx.rotation, n, Left); + x |= 2 * c!(ctx.cross[2], ctx.rotation, n, Up); + x |= c!(ctx.cross[3], ctx.rotation, n, Right); + U4::from(x) +} + +fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation) -> ImageHolder { + mask2tile(mask(ctx, name), rot, name) +} + +const FLIP_X: u8 = 1; +const FLIP_Y: u8 = 2; + +/// TODO figure out if a flip is cheaper than a rotate_270 +fn mask2tile(mask: U4, rot: Rotation, name: &str) -> ImageHolder { + use U4::*; + // let lo = |index: u8| { + // load("distribution/conveyors", &format!("{name}-{index}")) + // .unwrap() + // .value() + // }; + // r == 5 => flip_v + r - 1 + macro_rules! p { + ($image:literal, $rotation:literal) => { + ($image, $rotation, None) + }; + ($image:literal, $rotation:literal, $flipping:expr) => { + ($image, $rotation, Some($flipping)) + }; + } + + let (index, r, flip) = match mask { + // from left + B0001 => match rot { + Rotation::Down => p!(1, 1, FLIP_Y), // ┐ + Rotation::Right => p!(0, 0), // ─ + Rotation::Up => p!(1, 3), // ┘ + _ => unreachable!(), + }, + // from below + B0010 => match rot { + Rotation::Left => p!(1, 2), // ┐ + Rotation::Right => p!(1, 1), // ┌ + Rotation::Up => p!(0, 3), // │ + _ => unreachable!(), + }, + // from bottom + left + B0011 => match rot { + Rotation::Right => p!(2, 0), // ┬ + Rotation::Up => p!(2, 3, FLIP_Y | FLIP_X), // ┤ + _ => unreachable!(), + }, + // from right + B0100 => match rot { + Rotation::Left => p!(0, 2), // ─ + Rotation::Down => p!(1, 1), // ┌ + Rotation::Up => p!(1, 1, FLIP_X), // └ + _ => unreachable!(), + }, + // from sides + B0101 => match rot { + Rotation::Up => p!(4, 3), // ┴ + Rotation::Down => p!(4, 1), // ┬ + _ => unreachable!(), + }, + // from right + down + B0110 => match rot { + Rotation::Up => p!(2, 3), // ├, + Rotation::Left => p!(2, 0, FLIP_X), // ┬ + _ => unreachable!(), + }, + // from right + down + left + B0111 => match rot { + Rotation::Up => p!(3, 3), // ┼ + _ => unreachable!(), + }, + // from above + B1000 => match rot { + Rotation::Down => p!(0, 1), // │ + Rotation::Left => p!(1, 0, FLIP_X), // ┘ + Rotation::Right => p!(1, 0), // └ + _ => unreachable!(), + }, + // from top and left + B1001 => match rot { + Rotation::Right => p!(2, 0, FLIP_Y), // ┴ + Rotation::Down => p!(2, 1), // ┤ + _ => unreachable!(), + }, + // from top sides + B1010 => match rot { + Rotation::Right => p!(4, 0), // ├ + Rotation::Left => p!(4, 3), // ┤ + _ => unreachable!(), + }, + // from top, left, bottom + B1011 => match rot { + Rotation::Right => p!(3, 0), // ┼ + _ => unreachable!(), + }, + // from top and right + B1100 => match rot { + Rotation::Down => p!(2, 3, FLIP_X), // ├ + Rotation::Left => p!(2, 2), // ┴ + _ => unreachable!(), + }, + // from top, left, right + B1101 => match rot { + Rotation::Down => p!(3, 1), // ┼ + _ => unreachable!(), + }, + // from top, right, bottom + B1110 => match rot { + Rotation::Left => p!(3, 0, FLIP_X), // ┼ + _ => unreachable!(), + }, + B0000 => ( + 0, + match rot { + Rotation::Left => 2, + Rotation::Right => 0, + Rotation::Down => 1, + Rotation::Up => 3, + }, + None, + ), + // B0000 => (0, wrap(rot.count() as i8 - 1, 0, 3) as u8, None), + B1111 => unreachable!(), + }; + let mut p = ImageHolder::from(load("distribution/conveyors", &format!("{name}-{index}"))); + if let Some(op) = flip { + let re: &mut RgbaImage = p.borrow_mut(); + if (op & FLIP_X) != 0 { + flip_h(re); + } + if (op & FLIP_Y) != 0 { + flip_v(re); + } + } + if r == 0 { + return p; + } + let mut p = p.own(); + p.rotate(r); + ImageHolder::from(p) +} + +make_simple!( + ConveyorBlock, + |_, _, name, _, ctx: Option<&RenderingContext>| { + if let Some(ctx) = ctx { + return Some(tile(ctx, name, ctx.rotation)); + } + None + }, + true +); make_simple!( JunctionBlock, - |_, _, _, _| None, + |_, _, _, _, _| None, |_, _, _, _, _, buff: &mut crate::data::DataRead| { // format: // - iterate 4 @@ -26,34 +276,37 @@ make_simple!( buff.skip(n * 8)?; } Ok(()) - } + }, + false ); +make_simple!(ControlBlock); + make_register! { "conveyor" => ConveyorBlock::new(1, false, cost!(Copper: 1)); "titanium-conveyor" => ConveyorBlock::new(1, false, cost!(Copper: 1, Lead: 1, Titanium: 1)); - "plastanium-conveyor" => ConveyorBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1)); + "plastanium-conveyor" => ControlBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1)); "armored-conveyor" => ConveyorBlock::new(1, false, cost!(Metaglass: 1, Thorium: 1, Plastanium: 1)); "junction" => JunctionBlock::new(1, true, cost!(Copper: 2)); "bridge-conveyor" => BridgeBlock::new(1, false, cost!(Copper: 6, Lead: 6), 4, true); "phase-conveyor" => BridgeBlock::new(1, false, cost!(Lead: 10, Graphite: 10, Silicon: 7, PhaseFabric: 5), 12, true); "sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2)); "inverted-sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2)); - "router" => ConveyorBlock::new(1, true, cost!(Copper: 3)); - "distributor" => ConveyorBlock::new(2, true, cost!(Copper: 4, Lead: 4)); - "overflow-gate" => ConveyorBlock::new(1, true, cost!(Copper: 4, Lead: 2)); - "underflow-gate" => ConveyorBlock::new(1, true, cost!(Copper: 4, Lead: 2)); + "router" => ControlBlock::new(1, true, cost!(Copper: 3)); + "distributor" => ControlBlock::new(2, true, cost!(Copper: 4, Lead: 4)); + "overflow-gate" => ControlBlock::new(1, true, cost!(Copper: 4, Lead: 2)); + "underflow-gate" => ControlBlock::new(1, true, cost!(Copper: 4, Lead: 2)); "mass-driver" => BridgeBlock::new(3, true, cost!(Lead: 125, Titanium: 125, Thorium: 50, Silicon: 75), 55, false); "duct" => ConveyorBlock::new(1, false, cost!(Beryllium: 1)); "armored-duct" => ConveyorBlock::new(1, false, cost!(Beryllium: 2, Tungsten: 1)); "duct-router" => ItemBlock::new(1, true, cost!(Beryllium: 10)); - "overflow-duct" => ConveyorBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8)); - "underflow-duct" => ConveyorBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8)); + "overflow-duct" => ControlBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8)); + "underflow-duct" => ControlBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8)); "duct-bridge" => BridgeBlock::new(1, true, cost!(Beryllium: 20), 3, true); "duct-unloader" => ItemBlock::new(1, true, cost!(Graphite: 20, Silicon: 20, Tungsten: 10)); - "surge-conveyor" => ConveyorBlock::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1)); - "surge-router" => ConveyorBlock::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric - "unit-cargo-loader" => ConveyorBlock::new(3, true, cost!(Silicon: 80, SurgeAlloy: 50, Oxide: 20)); + "surge-conveyor" => ControlBlock::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1)); + "surge-router" => ControlBlock::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric + "unit-cargo-loader" => ControlBlock::new(3, true, cost!(Silicon: 80, SurgeAlloy: 50, Oxide: 20)); "unit-cargo-unload-point" => ItemBlock::new(2, true, cost!(Silicon: 60, Tungsten: 60)); // sandbox only "item-source" => ItemBlock::new(1, true, &[]); @@ -122,7 +375,13 @@ impl BlockLogic for ItemBlock { } } - fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<ImageHolder> { + fn draw( + &self, + category: &str, + name: &str, + state: Option<&State>, + _: Option<&RenderingContext>, + ) -> Option<ImageHolder> { if !matches!( name, "unloader" | "item-source" | "sorter" | "inverted-sorter" @@ -146,21 +405,16 @@ impl BlockLogic for ItemBlock { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)] +#[error("invalid config ({0}) for item")] pub struct ItemConvertError(pub i32); -impl fmt::Display for ItemConvertError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid config ({}) for item", self.0) - } -} - -impl Error for ItemConvertError {} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)] pub enum ItemDeserializeError { + #[error("expected Item but got {0:?}")] ContentType(content::Type), - NotFound(item::TryFromU16Error), + #[error("target item not found")] + NotFound(#[from] item::TryFromU16Error), } impl ItemDeserializeError { @@ -172,34 +426,6 @@ impl ItemDeserializeError { } } -impl From<item::TryFromU16Error> for ItemDeserializeError { - fn from(err: item::TryFromU16Error) -> Self { - Self::NotFound(err) - } -} - -impl fmt::Display for ItemDeserializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ContentType(have) => write!( - f, - "expected content {:?} but got {have:?}", - content::Type::Item - ), - Self::NotFound(..) => f.write_str("target item not found"), - } - } -} - -impl Error for ItemDeserializeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::NotFound(e) => Some(e), - _ => None, - } - } -} - pub struct BridgeBlock { size: u8, symmetric: bool, @@ -244,8 +470,8 @@ impl BlockLogic for BridgeBlock { y, }))); } - let dx = i32::from(x) - i32::from(pos.0); - let dy = i32::from(y) - i32::from(pos.1); + let dx = i32::from(x) - pos.0 as i32; + let dy = i32::from(y) - pos.1 as i32; Ok(DynData::Point2(dx, dy)) } @@ -334,16 +560,9 @@ impl BlockLogic for BridgeBlock { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)] +#[error("invalid coordinates ({x}, {y}) for bridge")] pub struct BridgeConvertError { pub x: i16, pub y: i16, } - -impl fmt::Display for BridgeConvertError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid coordinate ({} / {}) for bridge", self.x, self.y) - } -} - -impl Error for BridgeConvertError {} diff --git a/src/block/drills.rs b/src/block/drills.rs index 67d72f4..f3d2e8e 100644 --- a/src/block/drills.rs +++ b/src/block/drills.rs @@ -3,7 +3,7 @@ use crate::block::make_register; use crate::block::simple::{cost, make_simple}; use crate::data::renderer::*; -make_simple!(DrillBlock, |me: &DrillBlock, _, name, _| { +make_simple!(DrillBlock, |me: &DrillBlock, _, name, _, _| { if matches!(name, "cliff-crusher" | "large-plasma-bore" | "plasma-bore") { const SFX: &[&str; 3] = &["", "-top", "-rotator"]; return Some(ImageHolder::Own(read_with("drills", name, SFX, me.size))); diff --git a/src/block/environment.rs b/src/block/environment.rs index 9eee40c..0796230 100644 --- a/src/block/environment.rs +++ b/src/block/environment.rs @@ -11,7 +11,7 @@ macro_rules! register_env { $($field => EnvironmentBlock::new($size, true, &[]);)* ); - make_simple!(EnvironmentBlock, |_, _, name, _| { + make_simple!(EnvironmentBlock, |_, _, name, _, _| { let mut rand = StdRand::seed(ClockSeed::default().next_u64()); match name { $($field => { diff --git a/src/block/liquid.rs b/src/block/liquid.rs index 888bd0d..d74fc9d 100644 --- a/src/block/liquid.rs +++ b/src/block/liquid.rs @@ -1,6 +1,5 @@ //! liquid related things -use std::error::Error; -use std::fmt; +use thiserror::Error; use crate::block::distribution::BridgeBlock; use crate::block::simple::*; @@ -102,7 +101,13 @@ impl BlockLogic for FluidBlock { } } - fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<ImageHolder> { + fn draw( + &self, + category: &str, + name: &str, + state: Option<&State>, + _: Option<&RenderingContext>, + ) -> Option<ImageHolder> { let mut p = load(category, name).unwrap().clone(); if let Some(state) = state { if let Some(s) = Self::get_state(state) { @@ -117,21 +122,16 @@ impl BlockLogic for FluidBlock { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)] +#[error("invalid config ({0}) for fluid")] pub struct FluidConvertError(pub i32); -impl fmt::Display for FluidConvertError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid config ({}) for fluid", self.0) - } -} - -impl Error for FluidConvertError {} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)] pub enum FluidDeserializeError { + #[error("expected Fluid but got {0:?}")] ContentType(content::Type), - NotFound(fluid::TryFromU16Error), + #[error("fluid not found")] + NotFound(#[from] fluid::TryFromU16Error), } impl FluidDeserializeError { @@ -142,31 +142,3 @@ impl FluidDeserializeError { } } } - -impl From<fluid::TryFromU16Error> for FluidDeserializeError { - fn from(err: fluid::TryFromU16Error) -> Self { - Self::NotFound(err) - } -} - -impl fmt::Display for FluidDeserializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ContentType(have) => write!( - f, - "expected content {:?} but got {have:?}", - content::Type::Fluid - ), - Self::NotFound(..) => f.write_str("fluid not found"), - } - } -} - -impl Error for FluidDeserializeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::NotFound(e) => Some(e), - _ => None, - } - } -} diff --git a/src/block/logic.rs b/src/block/logic.rs index 24c7b2c..79941c4 100644 --- a/src/block/logic.rs +++ b/src/block/logic.rs @@ -1,18 +1,11 @@ //! logic processors and stuff use std::borrow::Cow; -use std::error::Error; -use std::fmt; use std::string::FromUtf8Error; -use flate2::{ - Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress, - FlushDecompress, Status, -}; - use crate::block::simple::*; use crate::block::*; use crate::data::dynamic::DynType; -use crate::data::{self, DataRead, DataWrite}; +use crate::data::{self, CompressError, DataRead, DataWrite}; make_simple!(LogicBlock); @@ -167,37 +160,9 @@ impl BlockLogic for ProcessorLogic { match data { DynData::Empty => Ok(Some(Self::create_state(ProcessorState::default()))), DynData::ByteArray(arr) => { - let mut input = arr.as_ref(); - let mut dec = Decompress::new(true); - let mut raw = Vec::<u8>::new(); - raw.reserve(1024); - loop { - let t_in = dec.total_in(); - let t_out = dec.total_out(); - let res = ProcessorDeserializeError::forward(dec.decompress_vec( - input, - &mut raw, - FlushDecompress::Finish, - ))?; - if dec.total_in() > t_in { - // we have to advance input every time, decompress_vec only knows the output position - input = &input[(dec.total_in() - t_in) as usize..]; - } - match res { - // there's no more input (and the flush mode says so), we need to reserve additional space - Status::Ok | Status::BufError => (), - // input was already at the end, so this is referring to the output - Status::StreamEnd => break, - } - if dec.total_in() == t_in && dec.total_out() == t_out { - // protect against looping forever - return Err(DeserializeError::Custom(Box::new( - ProcessorDeserializeError::DecompressStall, - ))); - } - raw.reserve(1024); - } - let mut buff = DataRead::new(&raw); + let input = arr.as_ref(); + let buff = DataRead::new(input).deflate()?; + let mut buff = DataRead::new(&buff); let ver = ProcessorDeserializeError::forward(buff.read_u8())?; if ver != 1 { return Err(DeserializeError::Custom(Box::new( @@ -279,48 +244,23 @@ impl BlockLogic for ProcessorLogic { ProcessorSerializeError::forward(rbuff.write_i16(link.x))?; ProcessorSerializeError::forward(rbuff.write_i16(link.y))?; } - let mut input = rbuff.get_written(); - let mut comp = Compress::new(Compression::default(), true); - let mut dst = Vec::<u8>::new(); - dst.reserve(1024); - loop { - let t_in = comp.total_in(); - let t_out = comp.total_out(); - let res = ProcessorSerializeError::forward(comp.compress_vec( - input, - &mut dst, - FlushCompress::Finish, - ))?; - if comp.total_in() > t_in { - // we have to advance input every time, compress_vec only knows the output position - input = &input[(comp.total_in() - t_in) as usize..]; - } - match res { - // there's no more input (and the flush mode says so), we need to reserve additional space - Status::Ok | Status::BufError => (), - // input was already at the end, so this is referring to the output - Status::StreamEnd => break, - } - if comp.total_in() == t_in && comp.total_out() == t_out { - // protect against looping forever - return Err(SerializeError::Custom(Box::new( - ProcessorSerializeError::CompressStall, - ))); - } - dst.reserve(1024); - } - Ok(DynData::ByteArray(dst)) + let mut out = DataWrite::default(); + rbuff.inflate(&mut out)?; + Ok(DynData::ByteArray(out.consume())) } } -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum ProcessorDeserializeError { - Read(data::ReadError), - Decompress(DecompressError), - DecompressStall, - FromUtf8(FromUtf8Error), + #[error("failed to read state data")] + Read(#[from] data::ReadError), + #[error("malformed utf-8 in processor code")] + FromUtf8(#[from] FromUtf8Error), + #[error("unsupported version ({0})")] Version(u8), + #[error("invalid code length ({0})")] CodeLength(i32), + #[error("invalid link count {0}")] LinkCount(i32), } @@ -333,54 +273,12 @@ impl ProcessorDeserializeError { } } -impl From<data::ReadError> for ProcessorDeserializeError { - fn from(value: data::ReadError) -> Self { - Self::Read(value) - } -} - -impl From<DecompressError> for ProcessorDeserializeError { - fn from(value: DecompressError) -> Self { - Self::Decompress(value) - } -} - -impl From<FromUtf8Error> for ProcessorDeserializeError { - fn from(value: FromUtf8Error) -> Self { - Self::FromUtf8(value) - } -} - -impl fmt::Display for ProcessorDeserializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Read(..) => f.write_str("failed to read state data"), - Self::Decompress(..) => f.write_str("zlib decompression failed"), - Self::DecompressStall => f.write_str("decompressor stalled before completion"), - Self::FromUtf8(..) => f.write_str("malformed utf-8 in processor code"), - Self::Version(ver) => write!(f, "unsupported version ({ver})"), - Self::CodeLength(len) => write!(f, "invalid code length ({len})"), - Self::LinkCount(cnt) => write!(f, "invalid link count ({cnt})"), - } - } -} - -impl Error for ProcessorDeserializeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Decompress(e) => Some(e), - Self::FromUtf8(e) => Some(e), - _ => None, - } - } -} - -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum ProcessorSerializeError { - Write(data::WriteError), - Compress(CompressError), - CompressEof(usize), - CompressStall, + #[error("failed to write state data")] + Write(#[from] data::WriteError), + #[error(transparent)] + Compress(#[from] CompressError), } impl ProcessorSerializeError { @@ -392,41 +290,6 @@ impl ProcessorSerializeError { } } -impl From<data::WriteError> for ProcessorSerializeError { - fn from(value: data::WriteError) -> Self { - Self::Write(value) - } -} - -impl From<CompressError> for ProcessorSerializeError { - fn from(value: CompressError) -> Self { - Self::Compress(value) - } -} - -impl fmt::Display for ProcessorSerializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Write(..) => f.write_str("failed to write state data"), - Self::Compress(..) => f.write_str("zlib compression failed"), - Self::CompressEof(remain) => write!( - f, - "compression overflow with {remain} bytes of input remaining" - ), - Self::CompressStall => f.write_str("compressor stalled before completion"), - } - } -} - -impl Error for ProcessorSerializeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Compress(e) => Some(e), - _ => None, - } - } -} - #[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct ProcessorLink { name: String, @@ -529,38 +392,18 @@ impl ProcessorState { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)] pub enum CodeError { + #[error("code too long ({0} bytes)")] TooLong(usize), } -impl fmt::Display for CodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TooLong(len) => write!(f, "code too long ({len} bytes)"), - } - } -} - -impl Error for CodeError {} - -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)] pub enum CreateError { + #[error("link name too long ({0} bytes)")] NameLength(usize), + #[error("there is already a link named {0}")] DuplicateName(String), + #[error("link {name} already points to ({x}, {y})")] DuplicatePos { name: String, x: i16, y: i16 }, } - -impl fmt::Display for CreateError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NameLength(len) => write!(f, "link name too long ({len} bytes)"), - Self::DuplicateName(name) => write!(f, "there already is a link named {name}"), - Self::DuplicatePos { name, x, y } => { - write!(f, "link {name} already points to {x} / {y}") - } - } - } -} - -impl Error for CreateError {} diff --git a/src/block/mod.rs b/src/block/mod.rs index 642518b..e5e8a49 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -11,26 +11,26 @@ use std::fmt; use crate::access::BoxAccess; use crate::data::dynamic::{DynData, DynType}; use crate::data::map::EntityMapping; -use crate::data::renderer::ImageHolder; +use crate::data::{self, renderer::*, CompressError}; use crate::data::{DataRead, GridPos, ReadError as DataReadError}; use crate::item::storage::ItemStorage; use crate::registry::RegistryEntry; -pub mod campaign; -pub mod content; -pub mod defense; -pub mod distribution; -pub mod drills; -pub mod environment; -pub mod liquid; -pub mod logic; -pub mod payload; -pub mod power; -pub mod production; -pub mod simple; -pub mod storage; -pub mod turrets; -pub mod walls; +macro_rules! mods { + ($($mod:ident)*) => { + $(pub mod $mod;)* + + pub mod all { + $(pub use crate::block::$mod::*;)* + } + } +} + +mods! { + campaign content defense distribution drills environment liquid logic payload power production storage turrets walls +} + +mod simple; pub type State = Box<dyn Any + Sync + Send>; pub trait BlockLogic { @@ -53,9 +53,21 @@ pub trait BlockLogic { fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError>; - fn draw(&self, _category: &str, _name: &str, _state: Option<&State>) -> Option<ImageHolder> { + #[allow(unused_variables)] + fn draw( + &self, + category: &str, + name: &str, + state: Option<&State>, + context: Option<&RenderingContext>, + ) -> Option<ImageHolder> { None } + + fn want_context(&self) -> bool { + false + } + // TODO: use data #[allow(unused_variables)] fn read( @@ -96,9 +108,10 @@ macro_rules! impl_block { } pub(crate) use impl_block; -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum DataConvertError { - Custom(Box<dyn Error + Sync + Send>), + #[error(transparent)] + Custom(#[from] Box<dyn Error + Sync + Send>), } impl DataConvertError { @@ -110,26 +123,14 @@ impl DataConvertError { } } -impl fmt::Display for DataConvertError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Custom(e) => e.fmt(f), - } - } -} - -impl Error for DataConvertError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Custom(e) => e.source(), - } - } -} - -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum DeserializeError { + #[error(transparent)] + DecompressError(#[from] data::DecompressError), + #[error("expected type {expect:?} but got {have:?}")] InvalidType { have: DynType, expect: DynType }, - Custom(Box<dyn Error + Sync + Send>), + #[error(transparent)] + Custom(#[from] Box<dyn Error + Sync + Send>), } impl DeserializeError { @@ -141,29 +142,12 @@ impl DeserializeError { } } -impl fmt::Display for DeserializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InvalidType { have, expect } => { - write!(f, "expected type {expect:?} but got {have:?}") - } - Self::Custom(e) => e.fmt(f), - } - } -} - -impl Error for DeserializeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Custom(e) => e.source(), - _ => None, - } - } -} - -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum SerializeError { - Custom(Box<dyn Error + Sync + Send>), + #[error(transparent)] + Custom(#[from] Box<dyn Error + Sync + Send>), + #[error(transparent)] + Compress(#[from] CompressError), } impl SerializeError { @@ -175,22 +159,6 @@ impl SerializeError { } } -impl fmt::Display for SerializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Custom(e) => e.fmt(f), - } - } -} - -impl Error for SerializeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Custom(e) => e.source(), - } - } -} - /// a block. put it in stuff! pub struct Block { category: Cow<'static, str>, @@ -227,12 +195,20 @@ impl Block { &self.name } + /// should you send context to [`image`]? + pub fn wants_context(&self) -> bool { + self.logic.as_ref().want_context() + } + /// draw this block, with this state - pub fn image(&self, state: Option<&State>) -> ImageHolder { - if let Some(p) = self.logic.as_ref().draw(&self.category, &self.name, state) { + pub fn image(&self, state: Option<&State>, context: Option<&RenderingContext>) -> ImageHolder { + if let Some(p) = self + .logic + .as_ref() + .draw(&self.category, &self.name, state, context) + { return p; } - use crate::data::renderer::read; ImageHolder::Own(read(&self.category, &self.name, self.get_size())) } @@ -301,7 +277,7 @@ impl Block { impl fmt::Debug for Block { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name: &str = &self.name; - write!(f, "Block {{ name: {name:?} }}") + write!(f, "Block<{name:?}>") } } @@ -322,6 +298,28 @@ pub enum Rotation { impl Rotation { #[must_use] + /// count rotations + pub fn count(self) -> u8 { + match self { + Self::Up => 0, + Self::Right => 1, + Self::Down => 2, + Self::Left => 3, + } + } + + #[must_use] + /// character of this rot (Right => >, Up => ^, Left => <, Down => v) + pub fn ch(self) -> char { + match self { + Rotation::Right => '>', + Rotation::Up => '^', + Rotation::Left => '<', + Rotation::Down => 'v', + } + } + + #[must_use] /// mirror the directions. pub fn mirrored(self, horizontally: bool, vertically: bool) -> Self { match self { @@ -453,7 +451,7 @@ macro_rules! make_register { std::borrow::Cow::Borrowed($field), $crate::access::Access::Borrowed(&$logic)); )+ - pub fn register(reg: &mut $crate::block::BlockRegistry<'_>) { + pub(crate) fn register(reg: &mut $crate::block::BlockRegistry<'_>) { $(assert!(reg.register(&[<$field:snake:upper>]).is_ok(), "duplicate block {:?}", $field);)+ } }}; diff --git a/src/block/payload.rs b/src/block/payload.rs index c743934..83f9700 100644 --- a/src/block/payload.rs +++ b/src/block/payload.rs @@ -1,6 +1,5 @@ //! payload related bits and bobs -use std::error::Error; -use std::fmt; +use thiserror::Error; use crate::block::content::Type as BlockEnum; use crate::block::distribution::BridgeBlock; @@ -145,35 +144,17 @@ impl BlockLogic for AssemblerBlock { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)] +#[error("invalid unit index ({idx}, valid: {count})")] pub struct AssemblerDeserializeError { pub idx: i32, pub count: i32, } -impl fmt::Display for AssemblerDeserializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "invalid unit index ({}, #valid: {})", - self.idx, self.count - ) - } -} - -impl Error for AssemblerDeserializeError {} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)] +#[error("invalid unit {0:?}")] pub struct AssemblerSerializeError(unit::Type); -impl fmt::Display for AssemblerSerializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid unit ({:?}) is not valid", self.0) - } -} - -impl Error for AssemblerSerializeError {} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Payload { Empty, @@ -291,11 +272,14 @@ impl BlockLogic for PayloadBlock { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)] pub enum PayloadDeserializeError { + #[error("expected Unit or Block but got {0:?}")] ContentType(content::Type), - BlockNotFound(block::content::TryFromU16Error), - UnitNotFound(unit::TryFromU16Error), + #[error("payload block not found")] + BlockNotFound(#[from] block::content::TryFromU16Error), + #[error("payload unit not found")] + UnitNotFound(#[from] unit::TryFromU16Error), } impl PayloadDeserializeError { @@ -306,40 +290,3 @@ impl PayloadDeserializeError { } } } - -impl From<block::content::TryFromU16Error> for PayloadDeserializeError { - fn from(err: block::content::TryFromU16Error) -> Self { - Self::BlockNotFound(err) - } -} - -impl From<unit::TryFromU16Error> for PayloadDeserializeError { - fn from(err: unit::TryFromU16Error) -> Self { - Self::UnitNotFound(err) - } -} - -impl fmt::Display for PayloadDeserializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ContentType(have) => write!( - f, - "expected content {:?} or {:?} but got {have:?}", - content::Type::Block, - content::Type::Unit - ), - Self::BlockNotFound(..) => f.write_str("payload block not found"), - Self::UnitNotFound(..) => f.write_str("payload unit not found"), - } - } -} - -impl Error for PayloadDeserializeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::BlockNotFound(e) => Some(e), - Self::UnitNotFound(e) => Some(e), - _ => None, - } - } -} diff --git a/src/block/power.rs b/src/block/power.rs index f5048c6..2c21cad 100644 --- a/src/block/power.rs +++ b/src/block/power.rs @@ -1,6 +1,5 @@ //! power connection and generation -use std::error::Error; -use std::fmt; +use thiserror::Error; use crate::block::simple::*; use crate::block::*; @@ -117,8 +116,9 @@ impl BlockLogic for ConnectorBlock { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ConnectorDeserializeError { + #[error("too many links ({have} but only {max} allowed)")] LinkCount { have: usize, max: u8 }, } @@ -131,18 +131,6 @@ impl ConnectorDeserializeError { } } -impl fmt::Display for ConnectorDeserializeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::LinkCount { have, max } => { - write!(f, "too many links ({have} but only {max} supported)") - } - } - } -} - -impl Error for ConnectorDeserializeError {} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct RGBA(u8, u8, u8, u8); diff --git a/src/block/production.rs b/src/block/production.rs index 7d8e7df..f5fa47b 100644 --- a/src/block/production.rs +++ b/src/block/production.rs @@ -44,7 +44,7 @@ make_register! { make_simple!( ProductionBlock, - |_, _, _, _| None, + |_, _, _, _, _| None, |_, _, _, _, _, buff: &mut crate::data::DataRead| { // format: // - progress: `f32` @@ -57,7 +57,7 @@ make_simple!( make_simple!( HeatCrafter, - |_, _, _, _| None, + |_, _, _, _, _| None, |_, _, _, _, _, buff: &mut crate::data::DataRead| { // format: // - progress: `f32` diff --git a/src/block/simple.rs b/src/block/simple.rs index 2dca7fd..89417c6 100644 --- a/src/block/simple.rs +++ b/src/block/simple.rs @@ -22,8 +22,10 @@ macro_rules! state_impl { pub(crate) use state_impl; +/// draw is called with self, category, name, state, context +/// read is called with self, category, name, reg, entity_mapping, buff macro_rules! make_simple { - ($name: ident, $draw: expr, $read: expr) => { + ($name: ident, $draw: expr, $read: expr, $wants_context: literal) => { pub struct $name { size: u8, symmetric: bool, @@ -87,8 +89,13 @@ macro_rules! make_simple { category: &str, name: &str, state: Option<&crate::block::State>, + context: Option<&crate::data::renderer::RenderingContext>, ) -> Option<crate::data::renderer::ImageHolder> { - $draw(self, category, name, state) + $draw(self, category, name, state, context) + } + + fn want_context(&self) -> bool { + $wants_context } fn read( @@ -104,10 +111,21 @@ macro_rules! make_simple { } }; ($name: ident, $draw: expr) => { - crate::block::simple::make_simple!($name, $draw, |_, _, _, _, _, _| Ok(())); + crate::block::simple::make_simple!($name, $draw, |_, _, _, _, _, _| Ok(()), false); + }; + ($name: ident, $draw: expr, $wants_context: literal) => { + crate::block::simple::make_simple!($name, $draw, |_, _, _, _, _, _| Ok(()), $wants_context); + }; + ($name: ident, $draw: expr, $read: expr) => { + crate::block::simple::make_simple!($name, $draw, $read, false); }; ($name: ident) => { - crate::block::simple::make_simple!($name, |_, _, _, _| None, |_, _, _, _, _, _| { Ok(()) }); + crate::block::simple::make_simple!( + $name, + |_, _, _, _, _| None, + |_, _, _, _, _, _| { Ok(()) }, + false + ); }; } pub(crate) use make_simple; diff --git a/src/block/turrets.rs b/src/block/turrets.rs index 160c991..1a931ea 100644 --- a/src/block/turrets.rs +++ b/src/block/turrets.rs @@ -34,7 +34,7 @@ make_register! { use crate::data::renderer::*; use crate::utils::ImageUtils; -crate::block::simple::make_simple!(TurretBlock, |me: &Self, _, name, _| { +crate::block::simple::make_simple!(TurretBlock, |me: &Self, _, name, _, _| { let path = match name { "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" | "scathe" | "malign" => format!("bases/reinforced-block-{}", me.size), diff --git a/src/block/walls.rs b/src/block/walls.rs index d746cdf..a0e3c45 100644 --- a/src/block/walls.rs +++ b/src/block/walls.rs @@ -4,7 +4,7 @@ use crate::block::*; use crate::data::dynamic::DynType; use crate::data::renderer::{load, read_with, ImageHolder, TOP}; -make_simple!(WallBlock, |_, _, name, _| { +make_simple!(WallBlock, |_, _, name, _, _| { if name == "thruster" { const SFX: &[&str; 1] = &[TOP]; return Some(ImageHolder::Own(read_with( diff --git a/src/data/map.rs b/src/data/map.rs index 4b1e5bd..5431d01 100644 --- a/src/data/map.rs +++ b/src/data/map.rs @@ -1,6 +1,6 @@ //! the map module //! ### format -//! note: utf = `len<u16>` + utf8(read(len)) +//! note: utf = `len<u16>` + `utf8(read(len))` //! //! note: each section has a `u32` denoting its length //! @@ -12,10 +12,10 @@ //! - tag section `<u32>` //! - 1 byte of idk (skip) //! - string map (`u16` for map len, iterate each, read `utf`) -//! - 4 bytes of idk (skip) //! - content header section `<u32>`: -//! - iterate `i8` (should = `8`)'//! - the type: `i8` (0: item, block: 1, liquid: 4, status: 5, unit: 6, weather: 7, sector: 9, planet: 13//! - item count: `u1\'6` (item: 22, block: 412, liquid: 11, status: 21, unit: 66, weather: 6, sector: 35, planet: 7) -//! - these types all have their own modules: [`crate::item`], [`crate::block::content`], [`crate::fluid`], [`crate::modifier`], [`crate::unit`], [`crate::data::weather`], [`crate::data::sector`], [`crate::data::planet`] +//! - iterate `i8` (should = `8`)' +//! - the type: `i8` (0: item, block: 1, liquid: 4, status: 5, unit: 6, weather: 7, sector: 9, planet: 13//! - item count: `u1\'6` (item: 22, block: 412, liquid: 11, status: 21, unit: 66, weather: 6, sector: 35, planet: 7) +//! - these types all have their own modules: [`item`], [`content`], [`fluid`], [`modifier`], [`mod@unit`], [`weather`], [`sector`], [`planet`] //! - iterate `u16` //! - name: `utf` //! - map section `<u32>` @@ -59,7 +59,7 @@ //! - team = `team#<u32>` //! - iterate `plans<u32>` //! - x: `u16`, y: `u16`, rot: `u16`, id: `u16` -//! - o: `DynData` (refer to [crate::data::dynamic::DynSerializer]) +//! - o: `DynData` (refer to [`DynSerializer`]) //! - world entities //! - iterate `u32` //! - len: `u16` @@ -70,33 +70,42 @@ //! - id: `u32` //! - entity read use std::collections::HashMap; +use std::ops::{Index, IndexMut}; use thiserror::Error; use crate::block::content::Type as BlockEnum; -use crate::block::{Block, BlockRegistry, Rotation}; +use crate::block::{environment, Block, BlockRegistry, Rotation}; use crate::data::dynamic::DynSerializer; use crate::data::renderer::*; use crate::data::DataRead; use crate::fluid::Type as Fluid; -use crate::item::storage::Storage; -use crate::item::Type as Item; +use crate::item::{storage::Storage, Type as Item}; use crate::team::Team; +#[allow(unused_imports)] +use crate::{block::content, data::*, fluid, item, modifier, unit}; -use super::GridPos; use super::Serializer; use crate::content::Content; use crate::utils::image::ImageUtils; /// a tile in a map +#[derive(Clone)] pub struct Tile<'l> { - pub pos: GridPos, pub floor: &'l Block, pub ore: Option<&'l Block>, - pub build: Option<Build<'l>>, + build: Option<Build<'l>>, } pub type EntityMapping = HashMap<u8, Box<dyn Content>>; impl<'l> Tile<'l> { + pub fn new(floor: &'l Block, ore: Option<&'l Block>) -> Self { + Self { + floor, + ore, + build: None, + } + } + fn set_block(&mut self, block: &'l Block) { self.build = Some(Build { block, @@ -108,6 +117,10 @@ impl<'l> Tile<'l> { }); } + pub fn build(&self) -> Option<&Build<'l>> { + self.build.as_ref() + } + /// check if this tile contains a building. pub fn has_building(&self) -> bool { if let Some(b) = &self.build { @@ -128,14 +141,14 @@ impl<'l> Tile<'l> { 1 } - pub fn image(&self) -> ImageHolder { + pub fn image(&self, context: Option<&RenderingContext>) -> ImageHolder { // building covers floore let i = if let Some(b) = &self.build { - b.image() + b.image(context) } else { - let mut i = self.floor.image(None).own(); + let mut i = self.floor.image(None, context).own(); if let Some(ore) = self.ore { - i.overlay(ore.image(None).borrow(), 0, 0); + i.overlay(ore.image(None, context).borrow(), 0, 0); } ImageHolder::from(i) }; @@ -147,14 +160,12 @@ impl std::fmt::Debug for Tile<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Tile<{},{}>@{}+{}{}", - self.pos.0, - self.pos.1, + "Tile@{}{}{}", self.floor.name(), if let Some(ore) = &self.ore { - ore.name() + format!("+{}", ore.name()) } else { - "" + "".into() }, if let Some(build) = &self.build { format!(":{}", build.block.name()) @@ -165,8 +176,32 @@ impl std::fmt::Debug for Tile<'_> { } } +impl<'l> BlockState<'l> for Tile<'l> { + fn get_block(&self) -> Option<&'l Block> { + Some(self.build()?.block) + } +} + +impl RotationState for Tile<'_> { + fn get_rotation(&self) -> Option<Rotation> { + Some(self.build()?.rotation) + } +} + +impl RotationState for Option<Tile<'_>> { + fn get_rotation(&self) -> Option<Rotation> { + self.as_ref().unwrap().get_rotation() + } +} + +impl<'l> BlockState<'l> for Option<Tile<'_>> { + fn get_block(&'l self) -> Option<&'l Block> { + self.as_ref().unwrap().get_block() + } +} + /// a build on a tile in a map -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Build<'l> { pub block: &'l Block, pub items: Storage<Item>, @@ -178,79 +213,163 @@ pub struct Build<'l> { } impl Build<'_> { - pub fn image(&self) -> ImageHolder { - self.block.image(None) + pub fn image(&self, context: Option<&RenderingContext>) -> ImageHolder { + self.block.image(None, context) } pub fn read( &mut self, buff: &mut DataRead<'_>, - reg: &BlockRegistry, - map: &EntityMapping, + _reg: &BlockRegistry, + _map: &EntityMapping, ) -> Result<(), ReadError> { // health let _ = buff.read_f32()?; // 4 - let rot = dbg!(buff.read_u8()?); // 5 + let rot = buff.read_u8()?; // 5 self.rotation = Rotation::try_from(rot & 127).unwrap_or(Rotation::Up); if (rot & 128) == 0 { return Err(ReadError::Version(rot & 128)); } - let _t = dbg!(buff.read_u8()?); // 6 - let _v = dbg!(buff.read_u8()?); // 7 - let mask = dbg!(buff.read_u8()?); // 8 - if dbg!((mask & 1) != 0) { - self.items.clear(); - // 10 - for _ in 0..dbg!(buff.read_u16()?) { - let item = buff.read_u16()?; - let amount = buff.read_u32()?; - if let Ok(item) = Item::try_from(item) { - self.items.set(item, amount); - } - } - } - if mask & 2 == 0 { - let n = buff.read_u16()? as usize; - buff.skip((n * 4) + 1)?; - } - if mask & 4 == 0 { - self.liquids.clear(); - for _ in 0..buff.read_u16()? { - let fluid = buff.read_u16()?; - let amount = buff.read_f32()?; - if let Ok(fluid) = Fluid::try_from(fluid) { - self.liquids.set(fluid, (amount * 100.0) as u32); - } - } - } + let _t = buff.read_u8()?; // 6 + let _v = buff.read_u8()?; // 7 + let _mask = buff.read_u8()?; // 8 + + // if (mask & 1) != 0 { + // self.items.clear(); + // // 10 + // for _ in 0..dbg!(buff.read_u16()?) { + // let item = buff.read_u16()?; + // let amount = buff.read_u32()?; + // if let Ok(item) = Item::try_from(item) { + // self.items.set(item, amount); + // } + // } + // } + // if mask & 2 == 0 { + // let n = buff.read_u16()? as usize; + // buff.skip((n * 4) + 1)?; + // } + // if mask & 4 == 0 { + // self.liquids.clear(); + // for _ in 0..buff.read_u16()? { + // let fluid = buff.read_u16()?; + // let amount = buff.read_f32()?; + // if let Ok(fluid) = Fluid::try_from(fluid) { + // self.liquids.set(fluid, (amount * 100.0) as u32); + // } + // } + // } // "efficiency"? - let _ = buff.read_u8()?; - let _ = buff.read_u8()?; + // let _ = buff.read_u8()?; + // let _ = buff.read_u8()?; // visible flags - let _ = buff.read_i64()?; - // "overriden by subclasses" - self.block.read(buff, reg, map)?; - Ok(()) + // let _ = buff.read_i64()?; + // implementation not complete, simply error, causing the remaining bytes in the chunk to be skipped (TODO finish impl) + Err(ReadError::Version(0x0)) + // "overridden by subclasses" + // self.block.read(buff, reg, map)?; + // Ok(()) } } -/// a map +/// a map. +/// ## Does not support serialization yet! #[derive(Debug)] pub struct Map<'l> { - pub width: u32, - pub height: u32, + pub width: usize, + pub height: usize, pub tags: HashMap<String, String>, + /// row major 2d array + /// ```rs + /// (0, 0), (1, 0), (2, 0) + /// (0, 1), (1, 1), (2, 1) + /// (0, 2), (1, 2), (2, 2) + /// ``` pub tiles: Vec<Tile<'l>>, } +macro_rules! cond { + ($cond: expr, $do: expr) => { + if $cond { + None + } else { + $do + } + }; +} + +impl<'l> Crossable for Map<'l> { + // N + // cond!(pos.position.1 >= (pos.height - 1) as u16, get(j + 1)), + // // E + // cond!( + // pos.position.0 >= (pos.height - 1) as u16, + // get(j + pos.height) + // ), + // // S + // cond!( + // pos.position.1 == 0 || pos.position.1 >= pos.height as u16, + // cond!(pos.position.1 >= (pos.height - 1), get(j + 1)), + // // E + // cond!(pos.position.0 >= (pos.height - 1), get(j + pos.height)), + // // S + // cond!( + // pos.position.1 == 0 || pos.position.1 >= pos.height, + // get(j - 1) + // ), + // // W + // cond!(j < pos.height, get(j - pos.height)), + fn cross(&self, j: usize, c: &PositionContext) -> Cross { + let get = |i| { + let b = &self[i]; + Some((b.get_block()?, b.get_rotation()?)) + }; + [ + cond![ + c.position.1 == 0 || c.position.1 >= c.height, + get(j + self.height) + ], + cond![c.position.0 >= (c.height - 1), get(j + 1)], + cond![c.position.1 >= (c.height - 1), get(j - self.width)], + cond![j < c.height, get(j - 1)], + ] + } +} + +impl<'l> Map<'l> { + pub fn new(width: usize, height: usize, tags: HashMap<String, String>) -> Self { + Self { + tiles: vec![Tile::new(&environment::STONE, None); width * height], + height, + width, + tags, + } + } +} + +impl<'l> Index<usize> for Map<'l> { + type Output = Tile<'l>; + fn index(&self, index: usize) -> &Self::Output { + &self.tiles[index] + } +} + +impl<'l> IndexMut<usize> for Map<'l> { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.tiles[index] + } +} + const MAP_HEADER: [u8; 4] = [b'M', b'S', b'A', b'V']; -/// error ocurring when reading a map fails +/// error occurring when reading a map fails #[derive(Debug, Error)] pub enum ReadError { #[error("failed to read from buffer")] Read(#[from] super::ReadError), + #[error(transparent)] + Decompress(#[from] super::DecompressError), #[error("incorrect header ({0:?})")] Header([u8; 4]), #[error("unsupported version ({0})")] @@ -286,7 +405,7 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> { return Err(ReadError::Version(version.try_into().unwrap_or(0))); } let mut tags = HashMap::new(); - buff.read_chunk(|buff| { + buff.read_chunk(true, |buff| { buff.skip(1)?; for _ in 0..buff.read_u8()? { let key = buff.read_utf()?; @@ -295,71 +414,40 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> { } Ok::<(), super::ReadError>(()) })?; - buff.read_chunk(|buff| { - // we skip these (just keep the respective modules updated) - for _ in 0..buff.read_i8()? { - // let _ty = buff.read_u8()?; - // for _ in 0..buff.read_i16()? { - // let name = dbg!(buff.read_utf()?); - // } - buff.skip(1)?; - for _ in 0..buff.read_u16()? { - let n = buff.read_u16()?; - buff.skip(n as usize)?; - } - } - Ok::<(), super::ReadError>(()) - })?; - - // map section + // we skip the content header (just keep the respective modules updated) + buff.skip_chunk()?; + // map section let mut w = 0; let mut h = 0; - let mut tiles = vec![]; - buff.read_chunk(|buff| { - w = buff.read_u16()? as u32; - h = buff.read_u16()? as u32; + let mut m = None; + buff.read_chunk(true, |buff| { + w = buff.read_u16()? as usize; + h = buff.read_u16()? as usize; + let mut map = Map::new(w, h, tags); let count = w * h; let mut i = 0; while i < count { - let x = (i % w) as u16; - let y = (i / w) as u16; let floor_id = buff.read_u16()?; let overlay_id = buff.read_u16()?; let floor = BlockEnum::try_from(floor_id) .unwrap_or(BlockEnum::Stone) .to(self.0) - .unwrap_or(&crate::block::environment::STONE); + .unwrap_or(&environment::STONE); let ore = BlockEnum::try_from(overlay_id) .unwrap_or(BlockEnum::Air) .to(self.0); - debug_assert!( - x < w as u16 && y < h as u16, - "{x} or {y} out of bounds ({floor:?} {ore:?})" - ); - tiles.push(Tile { - floor, - ore, - pos: GridPos(x, y), - build: None, - }); - let consecutives = buff.read_u8()? as u32; + map[i] = Tile::new(floor, ore); + let consecutives = buff.read_u8()? as usize; if consecutives > 0 { for i in (i + 1)..(i + 1 + consecutives) { - let x = (i % w) as u16; - let y = (i / w) as u16; - tiles.push(Tile { - floor, - ore, - pos: GridPos(x, y), - build: None, - }) + map[i] = Tile::new(floor, ore) } i += consecutives; } i += 1; } let mut i = 0usize; - while i < count as usize { + while i < count { let block_id = buff.read_u16()?; let packed = buff.read_u8()?; let entity = (packed & 1) != 0; @@ -378,30 +466,30 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> { }; if central { if let Some(block) = block { - tiles[i].set_block(block); + map[i].set_block(block); } } if entity { if central { - // TODO: actually read - let n = buff.read_u16()?; - buff.skip(n as usize)?; - // let _ = buff.read_i8()?; - // tiles[i] - // .build - // .as_mut() - // .unwrap() - // // map not initialized yet - // .read(&mut buff, self.0, &HashMap::new())?; + let _ = buff.read_chunk(false, |buff| { + let _ = buff.read_i8()?; + map[i] + .build + .as_mut() + .unwrap() + // map not initialized yet + .read(buff, self.0, &HashMap::new())?; + Ok::<(), ReadError>(()) + }); } } else if data { if let Some(block) = block { - tiles[i].set_block(block); + map[i].set_block(block); } - tiles[i].build.as_mut().unwrap().data = buff.read_i8()?; + map[i].build.as_mut().unwrap().data = buff.read_i8()?; } else { let consecutives = buff.read_u8()? as usize; - for tile in tiles.iter_mut().take(consecutives).skip(i + 1) { + for tile in map.tiles.iter_mut().take(consecutives).skip(i + 1) { if let Some(block) = block { tile.set_block(block); } @@ -410,10 +498,11 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> { } i += 1 } + m = Some(map); Ok::<(), ReadError>(()) })?; let mut mapping = EntityMapping::new(); - buff.read_chunk(|buff| { + buff.read_chunk(true, |buff| { for _ in 0..buff.read_u16()? { let id = buff.read_i16()? as u8; let nam = buff.read_utf()?; @@ -442,12 +531,7 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> { })?; // skip custom chunks buff.skip_chunk()?; - Ok(Map { - width: w, - height: h, - tags, - tiles, - }) + Ok(m.unwrap()) } /// serialize a map (todo) diff --git a/src/data/mod.rs b/src/data/mod.rs index 589a33c..c658210 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,7 +1,7 @@ //! all the IO use flate2::{ - Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress, - FlushDecompress, Status, + Compress, CompressError as CError, Compression, Decompress, DecompressError as DError, + FlushCompress, FlushDecompress, Status, }; use std::collections::HashMap; use std::error::Error; @@ -124,16 +124,28 @@ impl<'d> DataRead<'d> { Ok(len) } - pub fn read_chunk<E>(&mut self, f: impl FnOnce(&mut DataRead) -> Result<(), E>) -> Result<(), E> + pub fn read_chunk<E>( + &mut self, + big: bool, + f: impl FnOnce(&mut DataRead) -> Result<(), E>, + ) -> Result<(), E> where E: Error + From<ReadError>, { - let len = self.read_u32()? as usize; + let len = if big { + self.read_u32()? as usize + } else { + self.read_u16()? as usize + }; self.read = 0; let r = f(self); match r { Err(e) => { // skip this chunk + if len < self.read { + eprintln!("overread ({e:?})"); + return Err(e); + } let n = len - self.read; if n != 0 { self.skip(n)?; @@ -167,7 +179,7 @@ impl<'d> DataRead<'d> { Ok(()) } - pub fn deflate(&mut self) -> Result<Vec<u8>, ReadError> { + pub fn deflate(&mut self) -> Result<Vec<u8>, DecompressError> { let mut dec = Decompress::new(true); let mut raw = Vec::<u8>::new(); raw.reserve(1024); @@ -187,7 +199,7 @@ impl<'d> DataRead<'d> { } if dec.total_in() == t_in && dec.total_out() == t_out { // protect against looping forever - return Err(ReadError::DecompressStall); + return Err(DecompressError::DecompressStall); } raw.reserve(1024); } @@ -198,11 +210,15 @@ impl<'d> DataRead<'d> { } #[derive(Debug, Error)] -pub enum ReadError { +pub enum DecompressError { + #[error("zlib decompression failed")] + Decompress(#[from] DError), #[error("decompressor stalled before completion")] DecompressStall, - #[error("zlib decompession failed")] - Decompress(#[from] DecompressError), +} + +#[derive(Debug, Error)] +pub enum ReadError { #[error("buffer underflow (expected {need} but got {have})")] Underflow { need: usize, have: usize }, #[error("expected {0}")] @@ -309,7 +325,17 @@ impl<'d> DataWrite<'d> { } } - pub fn inflate(self, to: &mut DataWrite) -> Result<(), WriteError> { + /// eat this datawrite + /// + /// panics if ref write buffer + pub fn consume(self) -> Vec<u8> { + match self.data { + WriteBuff::Vec(v) => v, + WriteBuff::Ref { .. } => unreachable!(), + } + } + + pub fn inflate(self, to: &mut DataWrite) -> Result<(), CompressError> { // compress into the provided buffer let WriteBuff::Vec(raw) = self.data else { unreachable!("write buffer not owned") @@ -324,7 +350,7 @@ impl<'d> DataWrite<'d> { match comp.compress(&raw, &mut dst[*pos..], FlushCompress::Finish)? { // there's no more input (and the flush mode says so), but we can't resize the output Status::Ok | Status::BufError => { - return Err(WriteError::CompressEof( + return Err(CompressError::CompressEof( raw.len() - comp.total_in() as usize, )) } @@ -350,7 +376,7 @@ impl<'d> DataWrite<'d> { } if comp.total_in() == t_in && comp.total_out() == t_out { // protect against looping forever - return Err(WriteError::CompressStall); + return Err(CompressError::CompressStall); } dst.reserve(1024); } @@ -370,20 +396,21 @@ impl Default for DataWrite<'static> { } #[derive(Debug, Error)] +pub enum CompressError { + #[error(transparent)] + Compress(#[from] CError), + #[error("compression overflow with {0} bytes of input remaining")] + CompressEof(usize), + #[error("compressor stalled before completion")] + CompressStall, +} + +#[derive(Debug, Error)] pub enum WriteError { #[error("buffer overflow (expected {need} but got {have})")] Overflow { need: usize, have: usize }, #[error("string too long ({len} bytes of {})", u16::MAX)] TooLong { len: usize }, - #[error("zlib compression failed")] - Compress { - #[from] - source: CompressError, - }, - #[error("compression overflow with {0} bytes of input remaining")] - CompressEof(usize), - #[error("compressor stalled before completion")] - CompressStall, } impl PartialEq for WriteError { @@ -428,18 +455,28 @@ pub trait Serializer<D> { fn serialize(&mut self, buff: &mut DataWrite<'_>, data: &D) -> Result<(), Self::WriteError>; } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct GridPos(pub u16, pub u16); +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct GridPos(pub usize, pub usize); impl From<u32> for GridPos { fn from(value: u32) -> Self { - GridPos((value >> 16) as u16, value as u16) + GridPos((value >> 16) as u16 as usize, value as u16 as usize) } } impl From<GridPos> for u32 { + /// ``` + /// # use mindus::data::GridPos; + /// assert_eq!(GridPos::from(u32::from(GridPos(1000, 5))), GridPos(1000, 5)); + /// ``` fn from(value: GridPos) -> Self { - (u32::from(value.0) << 16) | u32::from(value.1) + ((value.0 << 16) | value.1) as u32 + } +} + +impl fmt::Debug for GridPos { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({0}, {1})", self.0, self.1) } } diff --git a/src/data/renderer.rs b/src/data/renderer.rs index a1af661..0454d3c 100644 --- a/src/data/renderer.rs +++ b/src/data/renderer.rs @@ -2,20 +2,21 @@ use dashmap::mapref::one::Ref; use dashmap::DashMap; use image::codecs::png::PngDecoder; -use image::{DynamicImage, RgbaImage}; +pub(crate) use image::{DynamicImage, RgbaImage}; use std::io::{BufReader, Cursor}; use std::path::{Path, PathBuf}; use std::sync::OnceLock; use zip::ZipArchive; use crate::block::environment::METAL_FLOOR; -use crate::data::map::Tile; +use crate::block::{Block, Rotation}; use crate::team::SHARDED; -use crate::utils::ImageUtils; +pub(crate) use crate::utils::ImageUtils; use crate::Map; -pub use std::borrow::Borrow; +pub(crate) use std::borrow::{Borrow, BorrowMut}; use super::schematic::Schematic; +use super::GridPos; type Cache = DashMap<PathBuf, RgbaImage>; fn cache() -> &'static Cache { @@ -45,6 +46,18 @@ impl Borrow<RgbaImage> for ImageHolder { } } +impl BorrowMut<RgbaImage> for ImageHolder { + fn borrow_mut(&mut self) -> &mut RgbaImage { + match self { + Self::Own(x) => x, + Self::Borrow(_) => { + *self = Self::from(std::mem::replace(self, Self::from(RgbaImage::new(0, 0))).own()); + self.borrow_mut() + } + } + } +} + impl From<Option<Ref<'static, PathBuf, RgbaImage>>> for ImageHolder { fn from(value: Option<Ref<'static, PathBuf, RgbaImage>>) -> Self { Self::Borrow(value.unwrap()) @@ -63,6 +76,61 @@ impl From<RgbaImage> for ImageHolder { } } +pub type Cross<'l> = [Option<(&'l Block, Rotation)>; 4]; +/// holds the 4 bordering blocks +#[derive(Copy, Clone)] +pub struct RenderingContext<'l> { + pub cross: Cross<'l>, + pub rotation: Rotation, + pub position: PositionContext, +} + +/// holds positions +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct PositionContext { + pub position: GridPos, + pub width: usize, + pub height: usize, +} + +impl std::fmt::Debug for PositionContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "PC<{:?} ({}/{})>", + self.position, self.width, self.height + ) + } +} + +impl std::fmt::Debug for RenderingContext<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} + +impl std::fmt::Display for RenderingContext<'_> { + /// this display impl shows RC<$directions=+own rotation> + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "RC<")?; + macro_rules! f { + ($f:expr, $z:expr, $x:literal, $at: expr, $srot: expr) => { + if let Some((_, rot)) = $z { + if (rot == $at && rot.mirrored(true, true) != $srot) { + $f.write_str($x)?; + } + } + }; + } + f!(f, self.cross[0], "N = ", Rotation::Down, self.rotation); + f!(f, self.cross[1], "E = ", Rotation::Left, self.rotation); + f!(f, self.cross[2], "S = ", Rotation::Up, self.rotation); + f!(f, self.cross[3], "W = ", Rotation::Right, self.rotation); + + write!(f, "{:?}>", self.rotation) + } +} + static CACHE: OnceLock<Cache> = OnceLock::new(); pub(crate) fn load(category: &str, name: &str) -> Option<Ref<'static, PathBuf, RgbaImage>> { let key = Path::new("blocks").join(category).join(name); @@ -83,7 +151,10 @@ pub(crate) fn load(category: &str, name: &str) -> Option<Ref<'static, PathBuf, R fn load_raw(f: impl AsRef<Path>) -> Option<RgbaImage> { let f = std::fs::File::open(Path::new("target/out").join(f)).ok()?; let r = PngDecoder::new(BufReader::new(f)).unwrap(); - Some(DynamicImage::from_decoder(r).unwrap().into_rgba8()) + let p = DynamicImage::from_decoder(r).unwrap().into_rgba8(); + assert!(p.width() != 0); + assert!(p.height() != 0); + Some(p) } fn load_zip() { @@ -128,61 +199,271 @@ where c } -/// renderer for creating images of schematics -pub struct Renderer {} -impl<'l> Renderer { - /// creates a picture of a schematic. Bridges and node connections are not drawn, and there is no background. - /// conveyors, conduits, and ducts currently do not render. +pub trait RotationState { + fn get_rotation(&self) -> Option<Rotation>; +} +pub trait BlockState<'l> { + fn get_block(&'l self) -> Option<&'l Block>; +} +pub(crate) trait Crossable { + fn cross(&self, j: usize, c: &PositionContext) -> Cross; +} + +// pub(crate) trait Darray { +// type Output; +// fn n(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>; +// fn e(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>; +// fn s(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>; +// fn w(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>; +// } + +#[cfg(test)] +fn print_crosses(v: Vec<Cross<'_>>, height: usize) -> String { + let mut s = String::new(); + for c in v.chunks(height) { + for c in c { + s.push(c[0].map_or('_', |(_, r)| r.ch())); + for c in c[1..].iter() { + s.push(','); + s.push(c.map_or('_', |(_, r)| r.ch())); + } + s.push(' '); + } + s.push('\n'); + } + s +} + +#[test] +fn test_cross() { + use crate::block::distribution::define; + let mut reg = crate::block::BlockRegistry::default(); + crate::block::distribution::register(&mut reg); + let mut ss = super::schematic::SchematicSerializer(®); + macro_rules! test { + ($schem: literal => $($a:tt,$b:tt,$c:tt,$d:tt)*) => { + let s = ss.deserialize_base64($schem).unwrap(); + let mut c = vec![]; + println!("{:#?}", s.blocks); + + for (position, _) in s.block_iter() { + let pctx = PositionContext { + position, + width: s.width, + height: s.height, + }; + c.push(s.cross(&pctx)); + } + let n = s.tags.get("name").map_or("<unknown>", |x| &x); + let cc: Vec<Cross> = vec![ + $(define!($a,$b,$c,$d),)* + ]; + if cc != c { + let a = print_crosses(cc, s.height as usize); + let b = print_crosses(c, s.height as usize); + for diff in diff::lines(&a, &b) { + match diff { + diff::Result::Left(l) => println!("\x1b[38;5;1m{}", l), + diff::Result::Right(r) => println!("\x1b[38;5;2m{}", r), + diff::Result::Both(l, _) => println!("\x1b[0m{}", l), + } + } + print!("\x1b[0m"); + /* + for diff in diff::slice(&c.into_iter().enumerate().collect::<Vec<_>>(), &cc.into_iter().enumerate().collect::<Vec<_>>()) { + match diff { + diff::Result::Left((i, l)) => println!("\x1b[38;5;1m- {l:?} at {i}"), + diff::Result::Right((i, r)) => println!("\x1b[38;5;2m+ {r:?} at {i}"), + diff::Result::Both((i, l), _) => println!("\x1b[0m {l:?} at {i}"), + } + } + */ + panic!("test {n} \x1b[38;5;1mfailed\x1b[0m") + } + println!("test {n} \x1b[38;5;2mpassed\x1b[0m"); + }; + } + // crosses go from bottom left -> top left -> bottom left + 1 -> top left + 1... + // the symbols are directions (> => Right...), which mean the neighbors pointing direction + // _ = no block + + // the basic test + // ─┐ + // ─┤ + test!("bXNjaAF4nGNgYmBiZmDJS8xNZWBNSizOTGbgTkktTi7KLCjJzM9jYGBgy0lMSs0pZmCNfr9gTSwjA0dyfl5ZamV+EVCOhQEBGGEEM4hiZGAGAOb+EWA=" => + // (0, 0) (0, 1) + // n e s w borders (west void for first row) + >,v,_,_ _,v,>,_ + // (1, 0) (1, 1) + v,_,_,> _,_,v,> + ); + // the loop test + // ─│─ + // ─┼┐ + // ─└┘ + test!("bXNjaAF4nDWK4QqAIBCDd6dE0SNGP8zuh2CeaAS9fZk0xvjGBgNjYJM7BDaqy5h3qb6EfAZNAIboNokVvKyE0Wu65NbyDhM+cQv6mTtTM/WFYfqLm6m3lx9MAg7n" => + >,^,_,_ <,>,<,_ _,v,>,_ + >,<,_,< v,v,^,> _,>,>,< + v,_,_,^ >,_,<,> _,_,v,v + ); + // the snek test + // └┐ + // ─┘ + test!("bXNjaAF4nGNgYmBiZmDJS8xNZWApzkvNZuBOSS1OLsosKMnMz2NgYGDLSUxKzSlmYIqOZWTgSM7PK0utzC8CSrAwIAAjEIIQhGJkYAIARA0Ozg==" => + ^,^,_,_ _,<,>,_ + <,_,_,> _,_,^,^ + ); + + // the notile test + test!("bXNjaAF4nCWJQQqAIBREx69E0Lq994oWph8STEMj6fZpzcDjDQMCSahoDsZsdN1TYB25aucz28uniMlxsdmf3wCGYDYOBbSsAqNN8eYn5XYofJEdAtSB31tfaoIVGw==" => + <,>,_,_ _,^,v,_ + ^,_,_,v _,_,>,< + ); + // the asymmetrical test + // <─── + // ───> + test!("bXNjaAF4nEXJwQqAIBAE0HGVCPrE6GC2B0HdcCPw78MKnMMwj4EFWbjiM8N5bRnLwRpqPK8oBcCU/M5JQetmMAcpNzep/cCIAfX69yv6RF0PFy0O4Q==" => + <,>,_,_ _,<,>,_ + <,>,_,> _,<,>,< + // <,_,_,> _,_,>,< + <,_,_,> _,_,>,< + ); + + // the complex test + // ─┬─│││─ + // ─┤─┘─┘─ + // ─┤┌─│─┐ + // ─┼┘─┴─│ + test!("bXNjaAF4nEWOUQ7CIBBEh2VZTbyCx/A2xg9a+WiC0LTGxNvb7Wjk5wEzb7M4QCO05UdBqj3PF5zuZR2XaX5OvQGwmodSV8j1FnAce3uVd1+24Iz/CYQQ8fcVHYEQIjqEXWEm9LwgX9kR+PLSbm2BMlN6Sk/3LhJnJu6S6CVmxl2MntEzv38AchUPug==" => + >,v,_,_ >,v,>,_ >,v,>,_ _,v,>,_ + v,<,_,> v,v,v,> v,>,v,> _,<,v,> + v,>,_,v >,<,<,v <,^,v,v _,v,>,v + <,>,_,< ^,v,>,v v,>,<,> _,^,^,< + v,<,_,> >,>,>,< ^,^,v,^ _,^,>,v + >,v,_,> ^,v,<,v ^,>,>,> _,>,^,^ + // <,_,_,< ^,_,>,v v,_,<,> _,_,^,< + // v,_,_,> >,_,>,< ^,_,v,^ _,_,>,v + // >,_,_,> ^,_,<,v ^,_,>,> _,_,^,^ + v,_,_,< >,_,v,> >,_,v,^ _,_,>,^ + ); +} + +/// trait for renderable objects +pub trait Renderable { + /// creates a picture of a schematic. Bridges and node connections are not drawn. + fn render(&self) -> RgbaImage; +} + +impl Renderable for Schematic<'_> { /// ``` /// use mindus::*; /// let mut s = Schematic::new(2, 3); /// s.put(0, 0, &block::distribution::DISTRIBUTOR); - /// s.put(0, 3, &block::distribution::ROUTER); - /// s.put(1, 3, &block::walls::COPPER_WALL); - /// let output /*: RgbaImage */ = Renderer::render_schematic(&s); + /// s.put(0, 2, &block::distribution::ROUTER); + /// s.put(1, 2, &block::walls::COPPER_WALL); + /// let output /*: RgbaImage */ = s.render(); /// ``` - pub fn render_schematic(s: &'l Schematic<'_>) -> RgbaImage { + fn render(&self) -> RgbaImage { + dbg!(&self.blocks.clone()); load_zip(); - let mut canvas = RgbaImage::new((s.width * 32).into(), (s.height * 32).into()); // fill background - canvas.repeat(METAL_FLOOR.image(None).borrow()); - for tile in s.block_iter() { - let x = (tile.pos.0 - ((tile.block.get_size() - 1) / 2) as u16) as u32; - let y = (s.height - tile.pos.1 - ((tile.block.get_size() / 2) + 1) as u16) as u32; - canvas.overlay(tile.image().borrow(), x * 32, y * 32); + let mut bg = RgbaImage::new( + ((self.width + 2) * 32) as u32, + ((self.height + 2) * 32) as u32, + ); + bg.repeat(METAL_FLOOR.image(None, None).borrow()); + let mut canvas = RgbaImage::new( + ((self.width + 2) * 32) as u32, + ((self.height + 2) * 32) as u32, + ); + for (GridPos(x, y), tile) in self.block_iter() { + let ctx = if tile.block.wants_context() { + let pctx = PositionContext { + position: GridPos(x, y), + width: self.width, + height: self.height, + }; + Some(RenderingContext { + cross: self.cross(&pctx), + rotation: tile.rot, + position: pctx, + }) + } else { + None + }; + #[cfg(debug_assertions)] + println!("rendering {tile:?} ({x}, {y}) [+{}]", tile.block.get_size()); + let x = x as u32 - ((tile.block.get_size() - 1) / 2) as u32; + let y = self.height as u32 - y as u32 - ((tile.block.get_size() / 2) + 1) as u32; + canvas.overlay( + tile.image(ctx.as_ref()).borrow(), + (x + 1) * 32, + (y + 1) * 32, + ); + // canvas.save("tmp.png").unwrap(); } - canvas + #[cfg(debug_assertions)] + println!("finishing up"); + image::imageops::overlay(&mut bg, canvas.shadow(), 0, 0); + bg } +} - pub fn render_map(m: &'l Map<'_>) -> RgbaImage { +impl Renderable for Map<'_> { + fn render(&self) -> RgbaImage { load_zip(); - let mut canvas = RgbaImage::new(m.width * 8, m.height * 8); - const VEC: Vec<&Tile<'_>> = vec![]; - let mut layers = [VEC; 2]; - for tile in m.tiles.iter() { - if tile.has_building() { - layers[1].push(tile) + let mut floor = RgbaImage::new(self.width as u32 * 8, self.height as u32 * 8); + let mut top = RgbaImage::new(self.width as u32 * 8, self.height as u32 * 8); + for (x, y, j, tile) in self.tiles.iter().enumerate().map(|(j, t)| { + ( + (j % self.width), + // flip y + (self.height - (j / self.width)) - 1, + j, + t, + ) + }) { + if tile.build().is_none() { + floor.overlay( + // SAFETY: [`load_raw`] forces nonzero image size + unsafe { &tile.image(None).own().scale(8) }, + x as u32 * 8, + y as u32 * 8, + ); } else { - layers[0].push(tile) - } - } - for tiles in layers { - for tile in tiles { - let s = if let Some(build) = &tile.build { + let s = if let Some(build) = &tile.build() { build.block.get_size() } else { 1 }; - let x = (tile.pos.0 - ((s - 1) / 2) as u16) as u32; - let y = (m.height as u16 - tile.pos.1 - ((s / 2) + 1) as u16) as u32; - canvas.overlay( - // SAFETY: surely not 0. (tile.size can never be 0). im not sure if you can load a 0 sized image.. but you might be able to. - unsafe { &tile.image().own().scale(tile.size() as u32 * 8) }, - x * 8, - y * 8, + let x = x - ((s - 1) / 2) as usize; + let y = y - (s / 2) as usize; + let ctx = (|| { + let b = tile.build()?; + if !b.block.wants_context() { + return None; + } + let pctx = PositionContext { + position: GridPos(x, y), + width: self.width, + height: self.height, + }; + let rctx = RenderingContext { + cross: self.cross(j, &pctx), + rotation: b.rotation, + position: pctx, + }; + Some(rctx) + })(); + top.overlay( + // SAFETY: tile.size can never be 0, and [`load_raw`] forces nonzero. + unsafe { &tile.image(ctx.as_ref()).own().scale(tile.size() as u32 * 8) }, + x as u32 * 8, + y as u32 * 8, ); } } - canvas + image::imageops::overlay(&mut floor, top.shadow(), 0, 0); + floor } } diff --git a/src/data/schematic.rs b/src/data/schematic.rs index 75378ba..2a71b3e 100644 --- a/src/data/schematic.rs +++ b/src/data/schematic.rs @@ -2,25 +2,24 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; use std::fmt::{self, Write}; -use std::iter::FusedIterator; -use std::slice::Iter; use thiserror::Error; use crate::block::{self, Block, BlockRegistry, Rotation, State}; use crate::data::base64; use crate::data::dynamic::{self, DynData, DynSerializer}; +use crate::data::renderer::*; use crate::data::{self, DataRead, DataWrite, GridPos, Serializer}; use crate::item::storage::ItemStorage; use crate::registry::RegistryEntry; +use crate::utils::array::Array2D; /// biggest schematic -pub const MAX_DIMENSION: u16 = 256; +pub const MAX_DIMENSION: usize = 256; /// most possible blocks pub const MAX_BLOCKS: u32 = 256 * 256; /// a placement in a schematic pub struct Placement<'l> { - pub pos: GridPos, pub block: &'l Block, pub rot: Rotation, state: Option<State>, @@ -28,11 +27,26 @@ pub struct Placement<'l> { impl PartialEq for Placement<'_> { fn eq(&self, rhs: &Placement<'_>) -> bool { - self.pos == rhs.pos && self.block == rhs.block && self.rot == rhs.rot + self.block == rhs.block && self.rot == rhs.rot + } +} + +impl fmt::Debug for Placement<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "P<{}[*{}]>", self.block.name(), self.rot.ch()) } } impl<'l> Placement<'l> { + /// make a placement from a block + pub fn new(block: &'l Block) -> Self { + Self { + block, + rot: Rotation::Up, + state: None, + } + } + /// gets the current state of this placement. you can cast it with `placement.block::get_state(placement.get_state()?)?` #[must_use] pub fn get_state(&self) -> Option<&State> { @@ -45,8 +59,8 @@ impl<'l> Placement<'l> { } /// draws this placement in particular - pub fn image(&self) -> crate::data::renderer::ImageHolder { - self.block.image(self.get_state()) + pub fn image(&self, context: Option<&RenderingContext>) -> ImageHolder { + self.block.image(self.get_state(), context) } /// set the state @@ -61,11 +75,40 @@ impl<'l> Placement<'l> { } } +impl<'l> BlockState<'l> for Placement<'l> { + fn get_block(&self) -> Option<&'l Block> { + Some(self.block) + } +} + +impl RotationState for Placement<'_> { + fn get_rotation(&self) -> Option<Rotation> { + Some(self.rot) + } +} + +impl<'l> BlockState<'l> for Option<Placement<'l>> { + fn get_block(&self) -> Option<&'l Block> { + let Some(p) = self else { + return None; + }; + Some(p.block) + } +} + +impl RotationState for Option<Placement<'_>> { + fn get_rotation(&self) -> Option<Rotation> { + let Some(p) = self else { + return None; + }; + Some(p.rot) + } +} + // manual impl because trait objects cannot be cloned impl<'l> Clone for Placement<'l> { fn clone(&self) -> Self { Self { - pos: self.pos, block: self.block, state: match self.state { None => None, @@ -76,14 +119,14 @@ impl<'l> Clone for Placement<'l> { } } -#[derive(Clone)] +#[derive(Clone, Debug)] /// a schematic. pub struct Schematic<'l> { - pub width: u16, - pub height: u16, + pub width: usize, + pub height: usize, pub tags: HashMap<String, String>, - pub blocks: Vec<Placement<'l>>, - lookup: Vec<Option<usize>>, + /// schems can have holes, so [Option] is used. + pub blocks: Array2D<Option<Placement<'l>>>, } impl<'l> PartialEq for Schematic<'l> { @@ -102,7 +145,7 @@ impl<'l> Schematic<'l> { /// # use mindus::Schematic; /// let s = Schematic::new(5, 5); /// ``` - pub fn new(width: u16, height: u16) -> Self { + pub fn new(width: usize, height: usize) -> Self { match Self::try_new(width, height) { Ok(s) => s, Err(NewError::Width(w)) => panic!("invalid schematic width ({w})"), @@ -110,12 +153,38 @@ impl<'l> Schematic<'l> { } } + /// the area around a point + pub(crate) fn cross(&self, c: &PositionContext) -> Cross { + let get = |x, y| { + let b = self.get(x?, y?).ok()??; + Some((b.get_block()?, b.get_rotation()?)) + }; + macro_rules! s { + ($x:expr) => { + Some($x) + }; + ($a:expr => $b:expr) => { + if $a < $b { + None + } else { + Some($a - $b) + } + }; + } + [ + get(s!(c.position.0), s!(c.position.1 + 1)), + get(s!(c.position.0 + 1), s!(c.position.1)), + get(s!(c.position.0), s!(c.position.1 => 1)), + get(s!(c.position.0 => 1), s!(c.position.1)), + ] + } + /// create a new schematic, erroring if too big /// ``` /// # use mindus::Schematic; /// assert!(Schematic::try_new(500, 500).is_err() == true); /// ``` - pub fn try_new(width: u16, height: u16) -> Result<Self, NewError> { + pub fn try_new(width: usize, height: usize) -> Result<Self, NewError> { if width > MAX_DIMENSION { return Err(NewError::Width(width)); } @@ -130,66 +199,52 @@ impl<'l> Schematic<'l> { width, height, tags, - blocks: Vec::new(), - lookup: Vec::new(), + blocks: Array2D::new(None, width, height), }) } - #[must_use] - /// have blocks? - pub fn is_empty(&self) -> bool { - self.blocks.is_empty() - } + // #[must_use] + // /// check if a rect is empty + // /// ``` + // /// # use mindus::Schematic; + // /// # use mindus::block::distribution::ROUTER; + // /// let mut s = Schematic::new(5, 5); + // /// s.put(0, 0, &ROUTER); + // /// assert!(s.is_region_empty(1, 1, 4, 4)); + // /// s.put(2, 2, &ROUTER); + // /// assert!(s.is_region_empty(1, 1, 4, 4) == false); + // /// // out of bounds is empty + // /// assert!(s.is_region_empty(25, 25, 0, 0)); + // /// ``` + // pub fn is_region_empty(&self, x: usize, y: usize, w: usize, h: usize) -> bool { + // if x >= self.width || y >= self.height || w == 0 || h == 0 { + // return true; + // } + // if w > 1 || h > 1 { + // for y in y..(y + h).min(self.height) { + // for x in x..(x + w).min(self.width) { + // if self.get(x, y).unwrap().is_some() { + // return false; + // } + // } + // } + // true + // } else { + // self.get(x, y).unwrap().is_none() + // } + // } - #[must_use] - /// count blocks - pub fn get_block_count(&self) -> usize { - self.blocks.len() - } - - #[must_use] - /// check if a rect is empty + /// gets a block /// ``` /// # use mindus::Schematic; - /// let s = Schematic::new(5, 5); - /// assert!(s.is_region_empty(1, 1, 4, 4) == true); + /// # use mindus::block::Rotation; + /// + /// let mut s = Schematic::new(5, 5); + /// assert!(s.get(0, 0).unwrap().is_none()); + /// s.put(0, 0, &mindus::block::turrets::DUO); + /// assert!(s.get(0, 0).unwrap().is_some()); /// ``` - pub fn is_region_empty(&self, x: u16, y: u16, w: u16, h: u16) -> bool { - if self.blocks.is_empty() { - return true; - } - if x >= self.width || y >= self.height || w == 0 || h == 0 { - return true; - } - if w > 1 || h > 1 { - let stride = self.width as usize; - let x_end = if self.width - x > w { - x + w - } else { - self.width - } as usize; - let y_end = if self.height - y > h { - y + h - } else { - self.height - } as usize; - let x = x as usize; - let y = y as usize; - for cy in y..y_end { - for cx in x..x_end { - if self.lookup[cx + cy * stride].is_some() { - return false; - } - } - } - true - } else { - self.lookup[(x as usize) + (y as usize) * (self.width as usize)].is_none() - } - } - - /// gets a block - pub fn get(&self, x: u16, y: u16) -> Result<Option<&Placement<'l>>, PosError> { + pub fn get(&self, x: usize, y: usize) -> Result<Option<&Placement<'l>>, PosError> { if x >= self.width || y >= self.height { return Err(PosError { x, @@ -198,18 +253,15 @@ impl<'l> Schematic<'l> { h: self.height, }); } - if self.blocks.is_empty() { - return Ok(None); - } - let pos = (x as usize) + (y as usize) * (self.width as usize); - match self.lookup[pos] { - None => Ok(None), - Some(idx) => Ok(Some(&self.blocks[idx])), + let b = &self.blocks[x][y]; + match b { + Some(b) => Ok(Some(b)), + _ => Ok(None), } } /// gets a block, mutably - pub fn get_mut(&mut self, x: u16, y: u16) -> Result<Option<&mut Placement<'l>>, PosError> { + pub fn get_mut(&mut self, x: usize, y: usize) -> Result<Option<&mut Placement<'l>>, PosError> { if x >= self.width || y >= self.height { return Err(PosError { x, @@ -218,68 +270,24 @@ impl<'l> Schematic<'l> { h: self.height, }); } - if self.blocks.is_empty() { - return Ok(None); - } - let pos = (x as usize) + (y as usize) * (self.width as usize); - match self.lookup[pos] { - None => Ok(None), - Some(idx) => Ok(Some(&mut self.blocks[idx])), + let b = &mut self.blocks[x][y]; + match b { + Some(b) => Ok(Some(b)), + _ => Ok(None), } } - fn remove(&mut self, idx: usize) -> Placement<'l> { - // swap_remove not only avoids moves in self.blocks but also reduces the lookup changes we have to do - let prev = self.blocks.swap_remove(idx); - self.fill_lookup( - prev.pos.0 as usize, - prev.pos.1 as usize, - prev.block.get_size() as usize, - None, - ); - if idx < self.blocks.len() { - // fix the swapped block's lookup entries - let swapped = &self.blocks[idx]; - self.fill_lookup( - swapped.pos.0 as usize, - swapped.pos.1 as usize, - swapped.block.get_size() as usize, - Some(idx), - ); - } - prev - } - - fn fill_lookup(&mut self, x: usize, y: usize, sz: usize, val: Option<usize>) { - if self.lookup.is_empty() { - self.lookup - .resize((self.width as usize) * (self.height as usize), None); - } - if sz > 1 { - let off = (sz - 1) / 2; - let (x0, y0) = (x - off, y - off); - for dy in 0..sz { - for dx in 0..sz { - self.lookup[(x0 + dx) + (y0 + dy) * (self.width as usize)] = val; - } - } - } else { - self.lookup[x + y * (self.width as usize)] = val; - } - } - - /// put a block in (same as [Schematic::set], but less arguments) + /// put a block in (same as [Schematic::set], but less arguments and builder-ness). panics!!! /// ``` /// # use mindus::Schematic; - /// # use mindus::DynData; - /// # use mindus::block::Rotation; /// /// let mut s = Schematic::new(5, 5); /// s.put(0, 0, &mindus::block::distribution::ROUTER); /// assert!(s.get(0, 0).unwrap().is_some() == true); /// ``` - pub fn put(&mut self, x: u16, y: u16, block: &'l Block) -> Result<&Placement<'l>, PlaceError> { - self.set(x, y, block, DynData::Empty, Rotation::Up) + pub fn put(&mut self, x: usize, y: usize, block: &'l Block) -> &mut Self { + self.set(x, y, block, DynData::Empty, Rotation::Up).unwrap(); + self } /// set a block @@ -294,58 +302,17 @@ impl<'l> Schematic<'l> { /// ``` pub fn set( &mut self, - x: u16, - y: u16, + x: usize, + y: usize, block: &'l Block, data: DynData, rot: Rotation, - ) -> Result<&Placement<'l>, PlaceError> { - let sz = u16::from(block.get_size()); - let off = (sz - 1) / 2; - if x < off || y < off { - return Err(PlaceError::Bounds { - x, - y, - sz: block.get_size(), - w: self.width, - h: self.height, - }); - } - if self.width - x < sz - off || self.height - y < sz - off { - return Err(PlaceError::Bounds { - x, - y, - sz: block.get_size(), - w: self.width, - h: self.height, - }); - } - if self.is_region_empty(x - off, y - off, sz, sz) { - let idx = self.blocks.len(); - let state = block.deserialize_state(data)?; - self.blocks.push(Placement { - pos: GridPos(x, y), - block, - state, - rot, - }); - self.fill_lookup(x as usize, y as usize, block.get_size() as usize, Some(idx)); - Ok(&self.blocks[idx]) - } else { - Err(PlaceError::Overlap { x, y }) - } - } - - pub fn replace( - &mut self, - x: u16, - y: u16, - block: &'l Block, - data: DynData, - rot: Rotation, - collect: bool, - ) -> Result<Option<Vec<Placement<'l>>>, PlaceError> { - let sz = u16::from(block.get_size()); + ) -> Result<(), PlaceError> { + println!( + "putting {block:?} at {x} / {y} ({}/{})", + self.width, self.height + ); + let sz = usize::from(block.get_size()); let off = (sz - 1) / 2; if x < off || y < off { return Err(PlaceError::Bounds { @@ -365,74 +332,17 @@ impl<'l> Schematic<'l> { h: self.height, }); } - if sz > 1 { - let mut result = if collect { Some(Vec::new()) } else { None }; - // remove all blocks in the region - for dy in 0..(sz as usize) { - for dx in 0..(sz as usize) { - if let Some(idx) = - self.lookup[(x as usize + dx) + (y as usize + dy) * (self.width as usize)] - { - let prev = self.remove(idx); - if let Some(ref mut v) = result { - v.push(prev); - } - } - } - } - let idx = self.blocks.len(); - let state = block.deserialize_state(data)?; - self.blocks.push(Placement { - pos: GridPos(x, y), - block, - state, - rot, - }); - self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx)); - Ok(result) - } else { - let pos = (x as usize) + (y as usize) * (self.width as usize); - match self.lookup[pos] { - None => { - let idx = self.blocks.len(); - let state = block.deserialize_state(data)?; - self.blocks.push(Placement { - pos: GridPos(x, y), - block, - state, - rot, - }); - self.lookup[pos] = Some(idx); - Ok(if collect { Some(Vec::new()) } else { None }) - } - Some(idx) => { - let state = block.deserialize_state(data)?; - let prev = std::mem::replace( - &mut self.blocks[idx], - Placement { - pos: GridPos(x, y), - block, - state, - rot, - }, - ); - self.fill_lookup( - prev.pos.0 as usize, - prev.pos.1 as usize, - prev.block.get_size() as usize, - None, - ); - self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx)); - Ok(if collect { Some(vec![prev]) } else { None }) - } - } - } + let state = block.deserialize_state(data)?; + let p = Placement { block, state, rot }; + self.blocks[x][y] = Some(p); + Ok(()) } /// take out a block /// ``` /// # use mindus::Schematic; /// # use mindus::DynData; + /// # use mindus::block::Rotation; /// /// let mut s = Schematic::new(5, 5); @@ -441,7 +351,7 @@ impl<'l> Schematic<'l> { /// assert!(s.take(0, 0).unwrap().is_some() == true); /// assert!(s.get(0, 0).unwrap().is_none() == true); /// ``` - pub fn take(&mut self, x: u16, y: u16) -> Result<Option<Placement<'l>>, PosError> { + pub fn take(&mut self, x: usize, y: usize) -> Result<Option<Placement<'l>>, PosError> { if x >= self.width || y >= self.height { return Err(PosError { x, @@ -450,207 +360,36 @@ impl<'l> Schematic<'l> { h: self.height, }); } - if self.blocks.is_empty() { - Ok(None) - } else { - let pos = (x as usize) + (y as usize) * (self.width as usize); - match self.lookup[pos] { - None => Ok(None), - Some(idx) => Ok(Some(self.remove(idx))), - } - } - } - - fn rebuild_lookup(&mut self) { - self.lookup.clear(); - if !self.blocks.is_empty() { - self.lookup - .resize((self.width as usize) * (self.height as usize), None); - for (i, curr) in self.blocks.iter().enumerate() { - let sz = curr.block.get_size() as usize; - let x = curr.pos.0 as usize - (sz - 1) / 2; - let y = curr.pos.1 as usize - (sz - 1) / 2; - if sz > 1 { - for dy in 0..sz { - for dx in 0..sz { - self.lookup[(x + dx) + (y + dy) * (self.width as usize)] = Some(i); - } - } - } else { - self.lookup[x + y * (self.width as usize)] = Some(i); - } - } - } - } - - /// flip it - pub fn mirror(&mut self, horizontally: bool, vertically: bool) { - if !self.blocks.is_empty() && (horizontally || vertically) { - for curr in &mut self.blocks { - // because position is the bottom left of the center (which changes during mirroring) - let shift = (u16::from(curr.block.get_size()) - 1) % 2; - if horizontally { - curr.pos.0 = self.width - 1 - curr.pos.0 - shift; - } - if vertically { - curr.pos.1 = self.height - 1 - curr.pos.1 - shift; - } - if !curr.block.is_symmetric() { - curr.rot.mirror(horizontally, vertically); - } - if let Some(ref mut state) = curr.state { - curr.block.mirror_state(state, horizontally, vertically); - } - } - self.rebuild_lookup(); - } - } - - /// turn - /// ``` - /// # use mindus::Schematic; - /// # use mindus::DynData; - /// # use mindus::block::Rotation; - /// - /// let mut s = Schematic::new(5, 5); - /// // 0, 0 == bottom left - /// s.put(0, 0, &mindus::block::turrets::HAIL); - /// s.rotate(true); - /// assert!(s.get(0, 4).unwrap().is_some() == true); - /// ``` - pub fn rotate(&mut self, clockwise: bool) { - let w = self.width; - let h = self.height; - self.width = h; - self.height = w; - if !self.blocks.is_empty() { - for curr in &mut self.blocks { - let x = curr.pos.0; - let y = curr.pos.1; - // because position is the bottom left of the center (which changes during rotation) - let shift = (u16::from(curr.block.get_size()) - 1) % 2; - if clockwise { - curr.pos.0 = y; - curr.pos.1 = w - 1 - x - shift; - } else { - curr.pos.0 = h - 1 - y - shift; - curr.pos.1 = x; - } - if !curr.block.is_symmetric() { - curr.rot.rotate(clockwise); - } - if let Some(ref mut state) = curr.state { - curr.block.rotate_state(state, clockwise); - } - } - self.rebuild_lookup(); - } - } - - /// resize this schematic - 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 { - let sz = u16::from(block.get_size()); - 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 || right > 0 || bottom > 0 { - return Err(TruncatedError { - right, - top, - left, - bottom, - })?; - } - self.width = w; - self.height = h; - for Placement { pos, .. } in &mut self.blocks { - pos.0 = (pos.0 as i16 + dx) as u16; - pos.1 = (pos.1 as i16 + dy) as u16; - } - Ok(()) - } - - /// like rotate(), but 180 - pub fn rotate_180(&mut self) { - self.mirror(true, true); - } - - #[must_use] - pub fn pos_iter(&self) -> PosIter { - PosIter { - x: 0, - y: 0, - w: self.width, - h: self.height, - } + let b = self.blocks[x][y].take(); + Ok(b) } /// iterate over all the blocks - pub fn block_iter<'s>(&'s self) -> Iter<'s, Placement<'l>> { - self.blocks.iter() + pub fn block_iter(&self) -> impl Iterator<Item = (GridPos, &Placement<'_>)> { + self.blocks.iter().enumerate().filter_map(|(i, p)| { + let Some(p) = p else { + return None; + }; + Some((GridPos(i / self.height, i % self.height), p)) + }) } #[must_use] /// see how much this schematic costs. + /// returns (cost, is_sandbox) /// ``` /// # use mindus::Schematic; /// # use mindus::DynData; /// # use mindus::block::Rotation; /// /// let mut s = Schematic::new(5, 5); - /// s.put(0, 0, &mindus::block::turrets::CYCLONE); - /// // assert_eq!(s.compute_total_cost().0.get_total(), 405); + /// s.put(1, 1, &mindus::block::turrets::CYCLONE); + /// assert_eq!(s.compute_total_cost().0.get_total(), 405); /// ``` pub fn compute_total_cost(&self) -> (ItemStorage, bool) { let mut cost = ItemStorage::new(); let mut sandbox = false; - for &Placement { block, .. } in &self.blocks { + for &Placement { block, .. } in self.blocks.iter().filter_map(|b| b.as_ref()) { if let Some(curr) = block.get_build_cost() { cost.add_all(&curr, u32::MAX); } else { @@ -665,32 +404,32 @@ impl<'l> Schematic<'l> { #[derive(Copy, Clone, Debug, Eq, PartialEq, Error)] pub enum NewError { #[error("invalid schematic width ({0})")] - Width(u16), + Width(usize), #[error("invalid schematic height ({0})")] - Height(u16), + Height(usize), } /// error created by doing stuff out of bounds #[derive(Copy, Clone, Debug, Eq, PartialEq, Error)] #[error("position {x} / {y} out of bounds {w} / {h}")] pub struct PosError { - pub x: u16, - pub y: u16, - pub w: u16, - pub h: u16, + pub x: usize, + pub y: usize, + pub w: usize, + pub h: usize, } #[derive(Debug, Error)] pub enum PlaceError { #[error("invalid block placement {x} / {y} (size {sz}) within {w} / {h}")] Bounds { - x: u16, - y: u16, + x: usize, + y: usize, sz: u8, - w: u16, - h: u16, + w: usize, + h: usize, }, #[error("overlapping an existing block at {x} / {y}")] - Overlap { x: u16, y: u16 }, + Overlap { x: usize, y: usize }, #[error("block state deserialization failed")] Deserialize(#[from] block::DeserializeError), } @@ -746,180 +485,6 @@ impl fmt::Display for TruncatedError { } } -impl fmt::Debug for Schematic<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl<'l> fmt::Display for Schematic<'l> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /* - Because characters are about twice as tall as they are wide, two are used to represent a single block. - Each block has a single letter to describe what it is + an optional rotation. - For size-1 blocks, that's "*]" for symmetric and "*>", "*^", "*<", "*v" for rotations. - Larger blocks are formed using pipes, slashes and minuses to form a border, which is filled with spaces. - Then, the letter is placed inside followed by the rotation (if any). - */ - - // find unique letters for each block, more common blocks pick first - let mut name_cnt = HashMap::<&str, u16>::new(); - for p in &self.blocks { - match name_cnt.entry(p.block.get_name()) { - Entry::Occupied(mut e) => *e.get_mut() += 1, - Entry::Vacant(e) => { - e.insert(1); - } - } - } - // only needed the map for counting - let mut name_cnt = Vec::from_iter(name_cnt); - name_cnt.sort_by(|l, r| r.1.cmp(&l.1)); - // set for control characters, space, b'*', DEL and b">^<v]/|\\-" - let mut used = [ - 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0x01u8, 0xA4u8, 0x00u8, 0x50u8, 0x00u8, 0x00u8, 0x00u8, - 0x70u8, 0x00u8, 0x00u8, 0x40u8, 0x90u8, - ]; - let mut types = HashMap::<&str, char>::new(); - for &(name, _) in &name_cnt { - let mut found = false; - for c in name.chars() { - if c > ' ' && c <= '~' { - let upper = c.to_ascii_uppercase() as usize; - let lower = c.to_ascii_lowercase() as usize; - if used[upper >> 3] & (1 << (upper & 7)) == 0 { - found = true; - used[upper >> 3] |= 1 << (upper & 7); - types.insert(name, unsafe { char::from_u32_unchecked(upper as u32) }); - break; - } - if lower != upper && used[lower >> 3] & (1 << (lower & 7)) == 0 { - found = true; - used[lower >> 3] |= 1 << (lower & 7); - types.insert(name, unsafe { char::from_u32_unchecked(lower as u32) }); - break; - } - } - } - if !found { - // just take whatever symbol's still free (avoids collisions with letters) - match used.iter().enumerate().find(|(_, &v)| v != u8::MAX) { - // there's no more free symbols... how? use b'*' instead for all of them (reserved) - None => { - types.insert(name, '*'); - } - Some((i, v)) => { - let idx = i + v.trailing_ones() as usize; - used[idx >> 3] |= 1 << (idx & 7); - types.insert(name, unsafe { char::from_u32_unchecked(idx as u32) }); - } - } - } - } - - // coordinates start in the bottom left, so y starts at self.height - 1 - if self.blocks.is_empty() { - write!(f, "<empty {} * {}>", self.width, self.height)?; - } else { - for y in (0..self.height as usize).rev() { - let mut x = 0usize; - while x < self.width as usize { - if let Some(idx) = self.lookup[x + y * (self.width as usize)] { - let Placement { - pos, - block, - state: _, - rot, - } = self.blocks[idx]; - let c = *types.get(block.get_name()).unwrap(); - match block.get_size() as usize { - 0 => unreachable!(), - 1 => { - f.write_char(c)?; - match rot { - _ if block.is_symmetric() => f.write_char(']')?, - Rotation::Right => f.write_char('>')?, - Rotation::Up => f.write_char('^')?, - Rotation::Left => f.write_char('<')?, - Rotation::Down => f.write_char('v')?, - } - } - s => { - let y0 = pos.1 as usize - (s - 1) / 2; - if y == y0 + (s - 1) { - // top row, which looks like /---[...]---\ - f.write_char('/')?; - if s == 2 { - // label & rotation are in this row - f.write_char(c)?; - match rot { - _ if block.is_symmetric() => f.write_char('-')?, - Rotation::Right => f.write_char('>')?, - Rotation::Up => f.write_char('^')?, - Rotation::Left => f.write_char('<')?, - Rotation::Down => f.write_char('v')?, - } - } else { - // label & rotation are not in this row - for _ in 0..(2 * s - 2) { - f.write_char('-')?; - } - } - f.write_char('\\')?; - } else if y == y0 { - // bottom row, which looks like \---[...]---/ - f.write_char('\\')?; - for _ in 0..(2 * s - 2) { - f.write_char('-')?; - } - f.write_char('/')?; - } else if s > 2 && y == y0 + s / 2 { - // middle row with label - f.write_char('|')?; - for cx in 0..(2 * s - 2) { - if cx == s - 2 { - f.write_char(c)?; - } else if cx == s - 1 { - match rot { - _ if block.is_symmetric() => f.write_char(' ')?, - Rotation::Right => f.write_char('>')?, - Rotation::Up => f.write_char('^')?, - Rotation::Left => f.write_char('<')?, - Rotation::Down => f.write_char('v')?, - } - } else { - f.write_char(' ')?; - } - } - f.write_char('|')?; - } else { - // middle row, which looks like | [...] | - f.write_char('|')?; - for _ in 0..(2 * s - 2) { - f.write_char(' ')?; - } - f.write_char('|')?; - } - } - } - x += block.get_size() as usize; - } else { - f.write_str(" ")?; - x += 1; - } - } - writeln!(f)?; - } - // print the letters assigned to blocks - for (k, _) in name_cnt { - let v = *types.get(k).unwrap(); - write!(f, "\n({v}) {k}")?; - } - } - Ok(()) - } -} - const SCHEMATIC_HEADER: u32 = ((b'm' as u32) << 24) | ((b's' as u32) << 16) | ((b'c' as u32) << 8) | (b'h' as u32); @@ -940,12 +505,12 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> { } let buff = buff.deflate()?; let mut buff = DataRead::new(&buff); - let w = buff.read_i16()?; - let h = buff.read_i16()?; - if w < 0 || h < 0 || w as u16 > MAX_DIMENSION || h as u16 > MAX_DIMENSION { + let w = buff.read_i16()? as usize; + let h = buff.read_i16()? as usize; + if w > MAX_DIMENSION || h > MAX_DIMENSION { return Err(ReadError::Dimensions(w, h)); } - let mut schematic = Schematic::new(w as u16, h as u16); + let mut schematic = Schematic::new(w, h); buff.read_map(&mut schematic.tags)?; let num_table = buff.read_i8()?; if num_table < 0 { @@ -953,23 +518,23 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> { } let mut block_table = Vec::new(); block_table.reserve(num_table as usize); - for _ in 0..num_table { + for _ in 0..dbg!(num_table) { let name = buff.read_utf()?; match self.0.get(name) { None => return Err(ReadError::NoSuchBlock(name.to_owned())), Some(b) => block_table.push(b), } } - let num_blocks = buff.read_i32()?; + let num_blocks = dbg!(buff.read_i32()?); if num_blocks < 0 || num_blocks as u32 > MAX_BLOCKS { return Err(ReadError::BlockCount(num_blocks)); } for _ in 0..num_blocks { - let idx = buff.read_i8()?; + let idx = dbg!(buff.read_i8()?); if idx < 0 || idx as usize >= block_table.len() { return Err(ReadError::BlockIndex(idx, block_table.len())); } - let pos = GridPos::from(buff.read_u32()?); + let pos = dbg!(GridPos::from(buff.read_u32()?)); let block = block_table[idx as usize]; let config = if version < 1 { block.data_from_i32(buff.read_i32()?, pos)? @@ -1006,7 +571,9 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> { // use string keys here to avoid issues with different block refs with the same name let mut block_map = HashMap::new(); let mut block_table = Vec::new(); - for curr in &data.blocks { + let mut block_count = 0i32; + for curr in data.blocks.iter().filter_map(|b| b.as_ref()) { + block_count += 1; if let Entry::Vacant(e) = block_map.entry(curr.block.get_name()) { e.insert(block_table.len() as u32); block_table.push(curr.block.get_name()); @@ -1021,20 +588,17 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> { rbuff.write_utf(name)?; } // don't have to check data.blocks.len() because dimensions don't allow exceeding MAX_BLOCKS - rbuff.write_i32(data.blocks.len() as i32)?; - let mut num = 0; - for curr in &data.blocks { + rbuff.write_i32(block_count)?; + for (pos, curr) in data.block_iter() { rbuff.write_i8(block_map[curr.block.get_name()] as i8)?; - rbuff.write_u32(u32::from(curr.pos))?; + rbuff.write_u32(pos.into())?; let data = match curr.state { None => DynData::Empty, Some(ref s) => curr.block.serialize_state(s)?, }; DynSerializer.serialize(&mut rbuff, &data)?; rbuff.write_u8(curr.rot.into())?; - num += 1; } - assert_eq!(num, data.blocks.len()); rbuff.inflate(buff)?; Ok(()) } @@ -1048,8 +612,8 @@ pub enum ReadError { Header(u32), #[error("unsupported version ({0})")] Version(u8), - #[error("invalid schematic dimensions ({0} * {1})")] - Dimensions(i16, i16), + #[error("invalid schematic dimensions ({0} / {1})")] + Dimensions(usize, usize), #[error("invalid block table size ({0})")] TableSize(i8), #[error("unknown block {0:?}")] @@ -1064,6 +628,8 @@ pub enum ReadError { ReadState(#[from] dynamic::ReadError), #[error("deserialized block could not be placed")] Placement(#[from] PlaceError), + #[error(transparent)] + Decompress(#[from] super::DecompressError), } #[derive(Debug, Error)] @@ -1078,6 +644,8 @@ pub enum WriteError { StateSerialize(#[from] block::SerializeError), #[error("failed to write block data")] WriteState(#[from] dynamic::WriteError), + #[error(transparent)] + Compress(#[from] super::CompressError), } impl<'l> SchematicSerializer<'l> { @@ -1088,7 +656,7 @@ impl<'l> SchematicSerializer<'l> { /// let reg = build_registry(); /// let mut ss = SchematicSerializer(®); /// let s = ss.deserialize_base64(string).unwrap(); - /// assert!(s.get(0, 0).unwrap().unwrap().block.name() == "payload-router"); + /// assert!(s.get(1, 1).unwrap().unwrap().block.name() == "payload-router"); /// ``` pub fn deserialize_base64(&mut self, data: &str) -> Result<Schematic<'l>, R64Error> { let mut buff = Vec::<u8>::new(); @@ -1130,88 +698,46 @@ pub enum W64Error { Content(#[from] WriteError), } -pub struct PosIter { - x: u16, - y: u16, - w: u16, - h: u16, -} - -impl Iterator for PosIter { - type Item = GridPos; - - fn next(&mut self) -> Option<Self::Item> { - if self.w > 0 && self.y < self.h { - let p = GridPos(self.x, self.y); - self.x += 1; - if self.x == self.w { - self.x = 0; - self.y += 1; - } - Some(p) - } else { - None - } - } - - fn size_hint(&self) -> (usize, Option<usize>) { - let pos = (self.x as usize) + (self.y as usize) * (self.w as usize); - let end = (self.w as usize) * (self.h as usize); - (end - pos, Some(end - pos)) - } - - fn count(self) -> usize { - let pos = (self.x as usize) + (self.y as usize) * (self.w as usize); - let end = (self.w as usize) * (self.h as usize); - end - pos - } - - fn last(self) -> Option<Self::Item> { - // self.y < self.h implies self.h > 0 - if self.w > 0 && self.y < self.h { - Some(GridPos(self.w - 1, self.h - 1)) - } else { - None - } - } -} - -impl FusedIterator for PosIter {} - #[cfg(test)] mod test { use super::*; - - macro_rules! test_iter { - ($name:ident, $it:expr, $($val:expr),+) => { - #[test] - fn $name() { - let mut it = $it; - $(test_iter!(impl it, $val);)+ - } - }; - (impl $it:ident, $val:literal) => { - for _ in 0..$val { - assert_ne!($it.next(), None, "iterator returned None too early"); - } - }; - (impl $it:ident, $val:expr) => { - assert_eq!($it.next(), $val); - }; - } + fn unwrap_pretty<T, E: std::fmt::Display + std::error::Error>(r: Result<T, E>) -> T { + match r { + Ok(t) => t, + Err(e) => { + use std::error::Error; + eprintln!("{e}"); + let mut err_ref = &e as &dyn Error; + loop { + let Some(next) = err_ref.source() else { + panic!(); + }; + eprintln!("\tFrom: {next}"); + err_ref = next; + } + } + } + } macro_rules! test_schem { - ($name:ident, $($val:expr),+) => { + ($name:ident, $($val:expr);+;) => { #[test] fn $name() { let reg = crate::block::build_registry(); let mut ser = SchematicSerializer(®); $( - let parsed = ser.deserialize_base64($val).unwrap(); - println!("{}", parsed.tags.get("name").unwrap()); - let unparsed = ser.serialize_base64(&parsed).unwrap(); - let parsed2 = ser.deserialize_base64(&unparsed).unwrap(); - assert_eq!(parsed, parsed2); + let parsed = unwrap_pretty(ser.deserialize_base64($val)); + println!("\x1b[38;5;2mdeserialized\x1b[0m {}", parsed.tags.get("name").unwrap()); + let unparsed = unwrap_pretty(ser.serialize_base64(&parsed)); + println!("\x1b[38;5;2mserialized\x1b[0m {}", parsed.tags.get("name").unwrap()); + let parsed2 = unwrap_pretty(ser.deserialize_base64(&unparsed)); + println!("\x1b[38;5;2mredeserialized\x1b[0m {}", parsed.tags.get("name").unwrap()); + if parsed != parsed2 { + // TODO serialization currently lossy? missing right side mostly + parsed2.render().save("p2.png").unwrap(); + parsed.render().save("p1.png").unwrap(); + panic!("DIFFERENT! see `p1.png` != `p2.png`") + } )* } }; @@ -1219,22 +745,54 @@ mod test { test_schem! { ser_de, - "bXNjaAF4nCVNy07DMBCcvC1c4MBnoNz4G8TBSSxRycSRbVr646iHlmUc2/KOZ3dmFo9QDdrVfFkMb9Gsi5mgFxvncNzS0a8Aemcm6yLq948Bz2eTbBjtTwpmTj7gafs00Y6zX0/2Qt6dzLdLeNj8mbrVLxZ6ciamcQlH59BHH5iAYTKJeOGCF6AisFSoBxF55V+hJm1Lvwca8lpVIuzlS0eGLoMqTGUG6OLRJes3Mw40E5ijc2QedkPuU3DfLX0eHriDsgMapaScu9zkT26o5Uq8EmV/zS5vi4tr/wHvJE7M", - "bXNjaAF4nE2MzWrEMAyEJ7bjdOnPobDQvfUF8kSlhyTWFlOv3VWcQvv0lRwoawzSjL4ZHOAtXJ4uhEdi+oz8ek5bDCvuA60Lx68aSwbg0zRTWmHe3j2emWI+F14ojEvJYYsVD5RoqVzSzy8xDjNNlzGXQHi5gVO8SvnIZasCnW4uM8fwQf9tT9+Ua1OUV0GBI9ozHToY6IeDtaIACxkOnaoe1rVrg2RV1cP0CuycLA5+LxuUU+U055W0Yrb4sEcGNQ3u1NTh9iHmH6qaOTI=", - "bXNjaAF4nE2R226kQAxEzW1oYC5kopV23/IDfMw+R3ng0klaYehsD6w2+fqtamuiDILCLtvH9MgPaTLJl/5ipR3cy4MN9s2FB//PTVaayV7H4N5X5xcR2c39YOerpI9Pe/kVrFuefRjt1A3BTS+2G/0ybW6V41+7rDGyy9UGjNnGtQt+W78C7ZCcgVSD7S/d4kH8+W3q7P5sbrr1nb85N9DeznZcg58/PlFxx6V77tqNr/1lQOr0anuQ7eQCCn2QQ6Rvy+z7Cb7Ib9xSSJpICsGDV5bxoVJKxpSRLIdUKrVkBQoSkVxYJDuWq5SaNByboUEYJ5LgmFlZRhdejit6oDO5Uw/trDTqgWfgpCqFiiG91MVL7IJfLKck3NooyBDEZM4Gw+9jtJOEXgQZ7TQAJZSaM+POFd5TSWpIoVHEVsqrlUcX8xq+U2pi94wyCHZpICn625jAGdVy4DxGpdom2gXeKu2OIw+6w5E7UABnMgKO9CgxOukiHBGjmGz1dFp+VQO57cA7pUR4+wVvFd5q9x2aQT0r/Ew4k/FfPyvunjhGaPgPoVJdLw==", - "bXNjaAF4nD1TDUxTVxS+r6+vr30triCSVjLXiulKoAjMrJRkU8b4qSgLUAlIZ1rah7yt9L31h1LMMCNbRAQhYrKwOnEslhCcdmzJuohL1DjZT4FJtiVsoG5LFtzmGgGngHm790mam7x77ne+c945934HKIAcB2K3vYUGScXmWvM+TQ3jYhysG8idtNfhYTgfAw8ASFz2RtrlBaKG12VA6X1KMjg8fgfT6KLBJi7osfsYH21oYdpoD6A4NkB7DG7WSQOJl/X4IPYM426loeU0bABSv9vF2p3I1cI4PKyB87AO2gu9gGi1+10+kMTCiCYXGzActvtoWEY+ABhcIgzaOBCJ4EZICYDx6xAV86vCdx2IAS5QJJAEIRkQ4XAjAHSIIITBUCCGRwIuESCgheEIkwgYIpEAF4I3wSw9bWccTpvNmVkZy5raWT1p3r+vajJ2odyQb+HAW9HxvV556vfvpNy4oVUfDyq36Kyqe73xsdemprMyv52uAreYwcXzJaPU+aDp8fFM24nuzUvVqYo9yr7CjFT/aDDzUUz8S8W7g+X3VCpVnargblNubl4kI1q6J+cFPH2HS6VSF5xzZWhCyYCKO2FjqAEprB9WRsJbwNFFoLKhITRCQheBbByQCMAQQwow1I8M9oPJ2870npqvvq5RvvfFyYE3hjrLmst3TixrV0XSN08Uax/UrMSeHdmKDdj8uhh3Pef2Wa+qDljrj82pK+aM300sl0eTrC/rL3zzZKZhRWFMq+mLvvTZb0bbweGZL/85ywwnl4RLzR9MBdIGy0LJowOWHxoOY2EiaJ/7s7ZP0Tg2wjWb3y6Lm3IPRNonw/0yT/+lZsdFy/LmUEp2RojHl68B41zDx43WJ/qANkwdVOvGtxjzpgo/keUURn2XK6zerz9Km10w3Vb8Ww/t/UdmHyx7fXwEcPiP0w1Xx9f+/m/X/d13Wiees8yPnk69ePlS9Yuf9sQf1dvVB27mm68U+51Fj7emzS+mzw1jzwuvTKFXHoK30l9EXctVlozIiSPdpk5djrW965BmV1XW4qsp8kNXmtWztdklXXTa0u6lO0d1+GS3TV/Q95O+17+S23Hs5sIfP4e/uqvd9oo+p7u0cYiPb4+9f/L+Qn3PmuXDdDai/ev0ts69I9nuNTOXp9HfOmoy/a5Y9D2cYYsebq+cKgB1V9vXdYFfOz7vWiVCLNnUUVkLOGO9umVN0jl2KoIjYSINEzgUORoDBKAnJwSLTLikQOBSAoC0ABBAbMgDWYIuBBeFRE7CbBCXCAwxFBAJPRgCSAFADBlykokcZCKHFAkPbSRKRaFUUsRGUyZLTJksMWWyjSlDJKhfFALZmFAJdFPo1+gkQVKXw/EW8/zToeZ5fh0t/H+V6k8+", - "bXNjaAF4nGNgZ2BjZmDJS8xNZRByrkzOyc9LdctJLEpVT1F4v3AaA3dKanFyUWZBSWZ+HgMDA1tOYlJqTjEDU3QsFwN/eWJJapFuakVJUWJySX4RA3tSYglQpJKBPRliEgNXQX45UElefkoqA39SUWZKeqpucn5eWWolSDmQlVKaWcIgkpyfm1RaDLJDNz01L7UoEWQaf3JRZX5aTmlmim5uZkUqUCA3M7koX7egKD85tbgYqIIlIzEzB+gqQQYwYGYEEkwgxMjAAuQCKSYOZgam//8ZWP7/+/+HgZGZBSTPDlEGVMEKVssAooAMNqAWBpA0SCdQKTMDMzvEZAZGRhCXBZXLyv7///8cIC4AKgZaCOKGAHEh0DBWBpAKIAW0hQNkAR9Q16+KOXtDbhfNNhBQneu5XNV+o/0FSYFCtbrHC+dm3v/MnK3TnKUYGCv0+X3uygksNwzr3jbr755T/O3NuiOxk+7YX7lSoNb3oW3CUq7vKxq4bn1rUKqJsqldfsLX2KkoICQ679L8bW8fCLaY3K+LfGLIe6hoXlaW3iMvrsUq7Hc9Mq1W/OrydlRf+VK9+Ov1iSmsK1deCPKVPF69dG+I5RQ3qSf2PLmlH2bkLwgT4Q3V5+3qnBDPqcG1dNuqZfETim+6G0UqT9b5bGsUznqqb7GZxoxc5eUMT/JvvC79UdlruPvxJis9XhbeTZXLN+68WFB41+DkNRv7uZEGOr/2rvPPu8ZfyXRLI+zoUnmLXRu3+nz0EnP1Omq39TLX3c23cleZ8F62FSnMVCviO2H6tWXB7z2LpA02vleTOXiJK20BT+ADsencfQd0tlqrfQuoWut5dHaX1OP/KwIY5r66Zp4T9+2p241L0XvPfu5w/Zu3bNX77V89kt42zOLf9jJ9vk+msV1vy/Knlywv7Lh4NEY7fvHay0t3Sxo+2q918+je/O23P+50/qEWe45XqGzaR1vh0h1idRwBYZter2DKPJv559VDhbXSHzgin2x8PeXIKsvmtRIVB5V5b/1zcBP+f7VpfuP1OLcJKiePP7n8paroYrul0uF88dp5619+MN8Z7WT0p7DTUqftYOt3XqN51hGf+IVDH0UwcDKwAZMFMwCWiVNe" + "bXNjaAF4nCVNy07DMBCcvC1c4MBnoNz4G8TBSSxRycSRbVr646iHlmUc2/KOZ3dmFo9QDdrVfFkMb9Gsi5mgFxvncNzS0a8Aemcm6yLq948Bz2eTbBjtTwpmTj7gafs00Y6zX0/2Qt6dzLdLeNj8mbrVLxZ6ciamcQlH59BHH5iAYTKJeOGCF6AisFSoBxF55V+hJm1Lvwca8lpVIuzlS0eGLoMqTGUG6OLRJes3Mw40E5ijc2QedkPuU3DfLX0eHriDsgMapaScu9zkT26o5Uq8EmV/zS5vi4tr/wHvJE7M"; + "bXNjaAF4nE2MzWrEMAyEJ7bjdOnPobDQvfUF8kSlhyTWFlOv3VWcQvv0lRwoawzSjL4ZHOAtXJ4uhEdi+oz8ek5bDCvuA60Lx68aSwbg0zRTWmHe3j2emWI+F14ojEvJYYsVD5RoqVzSzy8xDjNNlzGXQHi5gVO8SvnIZasCnW4uM8fwQf9tT9+Ua1OUV0GBI9ozHToY6IeDtaIACxkOnaoe1rVrg2RV1cP0CuycLA5+LxuUU+U055W0Yrb4sEcGNQ3u1NTh9iHmH6qaOTI="; + "bXNjaAF4nE2R226kQAxEzW1oYC5kopV23/IDfMw+R3ng0klaYehsD6w2+fqtamuiDILCLtvH9MgPaTLJl/5ipR3cy4MN9s2FB//PTVaayV7H4N5X5xcR2c39YOerpI9Pe/kVrFuefRjt1A3BTS+2G/0ybW6V41+7rDGyy9UGjNnGtQt+W78C7ZCcgVSD7S/d4kH8+W3q7P5sbrr1nb85N9DeznZcg58/PlFxx6V77tqNr/1lQOr0anuQ7eQCCn2QQ6Rvy+z7Cb7Ib9xSSJpICsGDV5bxoVJKxpSRLIdUKrVkBQoSkVxYJDuWq5SaNByboUEYJ5LgmFlZRhdejit6oDO5Uw/trDTqgWfgpCqFiiG91MVL7IJfLKck3NooyBDEZM4Gw+9jtJOEXgQZ7TQAJZSaM+POFd5TSWpIoVHEVsqrlUcX8xq+U2pi94wyCHZpICn625jAGdVy4DxGpdom2gXeKu2OIw+6w5E7UABnMgKO9CgxOukiHBGjmGz1dFp+VQO57cA7pUR4+wVvFd5q9x2aQT0r/Ew4k/FfPyvunjhGaPgPoVJdLw=="; + "bXNjaAF4nD1TDUxTVxS+r6+vr30triCSVjLXiulKoAjMrJRkU8b4qSgLUAlIZ1rah7yt9L31h1LMMCNbRAQhYrKwOnEslhCcdmzJuohL1DjZT4FJtiVsoG5LFtzmGgGngHm790mam7x77ne+c945934HKIAcB2K3vYUGScXmWvM+TQ3jYhysG8idtNfhYTgfAw8ASFz2RtrlBaKG12VA6X1KMjg8fgfT6KLBJi7osfsYH21oYdpoD6A4NkB7DG7WSQOJl/X4IPYM426loeU0bABSv9vF2p3I1cI4PKyB87AO2gu9gGi1+10+kMTCiCYXGzActvtoWEY+ABhcIgzaOBCJ4EZICYDx6xAV86vCdx2IAS5QJJAEIRkQ4XAjAHSIIITBUCCGRwIuESCgheEIkwgYIpEAF4I3wSw9bWccTpvNmVkZy5raWT1p3r+vajJ2odyQb+HAW9HxvV556vfvpNy4oVUfDyq36Kyqe73xsdemprMyv52uAreYwcXzJaPU+aDp8fFM24nuzUvVqYo9yr7CjFT/aDDzUUz8S8W7g+X3VCpVnargblNubl4kI1q6J+cFPH2HS6VSF5xzZWhCyYCKO2FjqAEprB9WRsJbwNFFoLKhITRCQheBbByQCMAQQwow1I8M9oPJ2870npqvvq5RvvfFyYE3hjrLmst3TixrV0XSN08Uax/UrMSeHdmKDdj8uhh3Pef2Wa+qDljrj82pK+aM300sl0eTrC/rL3zzZKZhRWFMq+mLvvTZb0bbweGZL/85ywwnl4RLzR9MBdIGy0LJowOWHxoOY2EiaJ/7s7ZP0Tg2wjWb3y6Lm3IPRNonw/0yT/+lZsdFy/LmUEp2RojHl68B41zDx43WJ/qANkwdVOvGtxjzpgo/keUURn2XK6zerz9Km10w3Vb8Ww/t/UdmHyx7fXwEcPiP0w1Xx9f+/m/X/d13Wiees8yPnk69ePlS9Yuf9sQf1dvVB27mm68U+51Fj7emzS+mzw1jzwuvTKFXHoK30l9EXctVlozIiSPdpk5djrW965BmV1XW4qsp8kNXmtWztdklXXTa0u6lO0d1+GS3TV/Q95O+17+S23Hs5sIfP4e/uqvd9oo+p7u0cYiPb4+9f/L+Qn3PmuXDdDai/ev0ts69I9nuNTOXp9HfOmoy/a5Y9D2cYYsebq+cKgB1V9vXdYFfOz7vWiVCLNnUUVkLOGO9umVN0jl2KoIjYSINEzgUORoDBKAnJwSLTLikQOBSAoC0ABBAbMgDWYIuBBeFRE7CbBCXCAwxFBAJPRgCSAFADBlykokcZCKHFAkPbSRKRaFUUsRGUyZLTJksMWWyjSlDJKhfFALZmFAJdFPo1+gkQVKXw/EW8/zToeZ5fh0t/H+V6k8+"; + "bXNjaAF4nGNgZ2BjZmDJS8xNZRByrkzOyc9LdctJLEpVT1F4v3AaA3dKanFyUWZBSWZ+HgMDA1tOYlJqTjEDU3QsFwN/eWJJapFuakVJUWJySX4RA3tSYglQpJKBPRliEgNXQX45UElefkoqA39SUWZKeqpucn5eWWolSDmQlVKaWcIgkpyfm1RaDLJDNz01L7UoEWQaf3JRZX5aTmlmim5uZkUqUCA3M7koX7egKD85tbgYqIIlIzEzB+gqQQYwYGYEEkwgxMjAAuQCKSYOZgam//8ZWP7/+/+HgZGZBSTPDlEGVMEKVssAooAMNqAWBpA0SCdQKTMDMzvEZAZGRhCXBZXLyv7///8cIC4AKgZaCOKGAHEh0DBWBpAKIAW0hQNkAR9Q16+KOXtDbhfNNhBQneu5XNV+o/0FSYFCtbrHC+dm3v/MnK3TnKUYGCv0+X3uygksNwzr3jbr755T/O3NuiOxk+7YX7lSoNb3oW3CUq7vKxq4bn1rUKqJsqldfsLX2KkoICQ679L8bW8fCLaY3K+LfGLIe6hoXlaW3iMvrsUq7Hc9Mq1W/OrydlRf+VK9+Ov1iSmsK1deCPKVPF69dG+I5RQ3qSf2PLmlH2bkLwgT4Q3V5+3qnBDPqcG1dNuqZfETim+6G0UqT9b5bGsUznqqb7GZxoxc5eUMT/JvvC79UdlruPvxJis9XhbeTZXLN+68WFB41+DkNRv7uZEGOr/2rvPPu8ZfyXRLI+zoUnmLXRu3+nz0EnP1Omq39TLX3c23cleZ8F62FSnMVCviO2H6tWXB7z2LpA02vleTOXiJK20BT+ADsencfQd0tlqrfQuoWut5dHaX1OP/KwIY5r66Zp4T9+2p241L0XvPfu5w/Zu3bNX77V89kt42zOLf9jJ9vk+msV1vy/Knlywv7Lh4NEY7fvHay0t3Sxo+2q918+je/O23P+50/qEWe45XqGzaR1vh0h1idRwBYZter2DKPJv559VDhbXSHzgin2x8PeXIKsvmtRIVB5V5b/1zcBP+f7VpfuP1OLcJKiePP7n8paroYrul0uF88dp5619+MN8Z7WT0p7DTUqftYOt3XqN51hGf+IVDH0UwcDKwAZMFMwCWiVNe"; + "bXNjaAF4nGNgZGBkZmDJS8xNZeBMrShIzSvOLEtl4E5JLU4uyiwoyczPY2BgYMtJTErNKWZgio5lZODPzUwuytctKMpPTi0uzi8CyjMygAAfA4PQ+Yo5by9u9GxmZGB9GME502nTzKW+Aht/FJq1ez+o8nzYGn5n+wHR70VVf23t9tutu58/Xbm+qr5t/v+PAa93zIn+L1BpFbXfY17fNf1Jyxd/7X7yMuOv0qjQqNCo0KjQqNCo0KjQqNCo0KjQqNCo0KjQqNCo0KjQqNCoEJWFHp987V9uXjv/9y4GAOhu6pc="; + } + + #[test] + fn block_iter() { + macro_rules! test_iter { + ($it:ident, $($val:expr;)*) => { + $(assert_eq!($it.next().map(|(pos, p)| (pos, p.block)), $val);)* + }; + } + macro_rules! pair { + ($x:literal,$y:literal,$v:expr) => { + Some((GridPos($x, $y), &$v)) + }; + () => { + None + }; + } + use crate::block::all::*; + let mut s = Schematic::new(3, 3); + s.put(0, 0, &DISTRIBUTOR) + .put(0, 1, &JUNCTION) + .put(1, 1, &PHASE_CONVEYOR) + .put(2, 0, &ROUTER) + .put(1, 1, &CONVEYOR); + let mut it = s.block_iter(); + test_iter![ + it, + pair!(0, 0, DISTRIBUTOR); + pair!(0, 1, JUNCTION); + pair!(1, 1, CONVEYOR); + pair!(2, 0, ROUTER); + pair!( ); + ]; + let reg = crate::block::build_registry(); + let mut s = SchematicSerializer(®); + + let s = s.deserialize_base64("bXNjaAF4nDXKywqAIBQA0fFRBH1itDC7C8E01IT+vgia1VkMFmOwyR3C0N0VG/Mu1ZdwtpATMEa3SazoZdVMPqcudy7/DJovpV4peAAt0xF6").unwrap(); + let mut it = s.block_iter(); + test_iter![it, + pair!(0, 0, CONVEYOR); + pair!(2, 1, VAULT); + pair!(); + ]; } - - test_iter!( - block_iter, - Schematic::new(3, 4).pos_iter(), - Some(GridPos(0, 0)), - Some(GridPos(1, 0)), - Some(GridPos(2, 0)), - Some(GridPos(0, 1)), - 7, - Some(GridPos(2, 3)), - None - ); } diff --git a/src/exe/draw.rs b/src/exe/draw.rs index 0a95634..1b7830c 100644 --- a/src/exe/draw.rs +++ b/src/exe/draw.rs @@ -1,5 +1,5 @@ use mindus::build_registry; -use mindus::Renderer; +use mindus::Renderable; use mindus::SchematicSerializer; use std::env::Args; @@ -18,7 +18,7 @@ pub fn main(args: Args) { if !first || need_space { println!(); } - Renderer::render_schematic(&s).save("x.png").unwrap(); + s.render().save("x.png").unwrap(); } // continue processing literals & maybe interactive mode Err(e) => { diff --git a/src/exe/map.rs b/src/exe/map.rs index 13955cd..92653d8 100644 --- a/src/exe/map.rs +++ b/src/exe/map.rs @@ -1,5 +1,5 @@ use mindus::data::DataRead; -use mindus::{build_registry, Renderer}; +use mindus::{build_registry, Renderable}; use mindus::{MapSerializer, Serializer}; use std::env::Args; @@ -14,7 +14,7 @@ pub fn main(args: Args) { match ms.deserialize(&mut DataRead::new(&s)) { Err(e) => print_err!(e, "fail"), Ok(m) => { - Renderer::render_map(&m).save("x.png").unwrap(); + m.render().save("x.png").unwrap(); } } } diff --git a/src/exe/mod.rs b/src/exe/mod.rs index e56922a..aa49f29 100644 --- a/src/exe/mod.rs +++ b/src/exe/mod.rs @@ -1,23 +1,19 @@ mod draw; mod map; -mod print; macro_rules! print_err { - ($err:expr, $($msg:tt)*) => { - { - use std::error::Error; - let err = $err; - eprint!($($msg)*); - eprintln!(": {err}"); - let mut err_ref = &err as &dyn Error; - loop - { - if let Some(next) = err_ref.source() - { - eprintln!("\tSource: {next}"); - err_ref = next; - } - else {break;} + ($err:expr, $($msg:tt)*) => {{ + use std::error::Error; + let err = $err; + eprint!($($msg)*); + eprintln!(": {err}"); + let mut err_ref = &err as &dyn Error; + loop { + let Some(next) = err_ref.source() else { + break; + }; + eprintln!("\tSource: {next}"); + err_ref = next; } } }; @@ -28,10 +24,9 @@ fn main() { let mut args = std::env::args(); args.next().unwrap(); // path to executable match args.next() { - None => eprintln!("Not enough arguments, valid commands are: draw, print"), - Some(s) if s == "print" => print::main(args), + None => eprintln!("Not enough arguments, valid commands are: draw, map"), Some(s) if s == "draw" => draw::main(args), Some(s) if s == "map" => map::main(args), - Some(s) => eprintln!("Unknown argument {s}, valid commands are: draw, print"), + Some(s) => eprintln!("Unknown argument {s}, valid commands are: draw, map"), } } diff --git a/src/exe/print.rs b/src/exe/print.rs deleted file mode 100644 index 25af8a4..0000000 --- a/src/exe/print.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::env::Args; - -use mindus::build_registry; -use mindus::{Schematic, SchematicSerializer}; - -use crate::print_err; - -pub fn main(args: Args) { - let reg = build_registry(); - let mut ss = SchematicSerializer(®); - let mut first = true; - let mut need_space = false; - // process schematics from command line - for curr in args { - match ss.deserialize_base64(&curr) { - Ok(s) => { - if !first || need_space { - println!(); - } - first = false; - need_space = true; - println!("Schematic: {curr}"); - print_schematic(&s); - } - // continue processing literals & maybe interactive mode - Err(e) => { - if need_space { - println!(); - } - first = false; - need_space = false; - print_err!(e, "Could not read schematic"); - } - } - } -} - -pub fn print_schematic(s: &Schematic) { - if let Some(name) = s.tags.get("name") { - if !name.is_empty() { - println!("Name: {name}"); - } - } - if let Some(desc) = s.tags.get("description") { - if !desc.is_empty() { - println!("Desc: {desc}"); - } - } - if let Some(labels) = s.tags.get("labels") { - if !labels.is_empty() && labels != "[]" { - println!("Tags: {:?}", labels); - } - } - let (cost, sandbox) = s.compute_total_cost(); - if !cost.is_empty() { - println!( - "Build cost: {cost}{}", - if sandbox { " (Sandbox only)" } else { "" } - ); - } else if sandbox { - println!("Can only be built in the Sandbox"); - } - println!("\n{s}"); -} diff --git a/src/item/storage.rs b/src/item/storage.rs index 45397f5..6eeb018 100644 --- a/src/item/storage.rs +++ b/src/item/storage.rs @@ -1,4 +1,3 @@ -use std::error::Error; use std::fmt; use std::iter::{Enumerate, FusedIterator}; use std::marker::PhantomData; @@ -43,6 +42,12 @@ where } #[must_use] + /// total items + pub fn get_total(&self) -> u64 { + self.total + } + + #[must_use] /// check if its empty /// /// ``` @@ -379,44 +384,22 @@ where } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)] +#[error("adding {add} to current {have} would exceed {max}")] pub struct TryAddError { pub have: u32, pub add: u32, pub max: u32, } -impl fmt::Display for TryAddError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "adding {:?} to current {} would exceed {}", - self.add, self.have, self.max - ) - } -} - -impl Error for TryAddError {} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)] +#[error("removing {sub} from current {have} would drop below {min}")] pub struct TrySubError { pub have: u32, pub sub: u32, pub min: u32, } -impl fmt::Display for TrySubError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "removing {} from current {} would drop below {}", - self.sub, self.have, self.min - ) - } -} - -impl Error for TrySubError {} - #[derive(Clone, Debug)] pub struct Iter<'l> { base: Enumerate<slice::Iter<'l, u32>>, @@ -14,6 +14,6 @@ mod utils; pub use block::build_registry; pub use data::dynamic::DynData; pub use data::map::{Map, MapSerializer}; -pub use data::renderer::Renderer; +pub use data::renderer::Renderable; pub use data::schematic::{Schematic, SchematicSerializer}; pub use data::Serializer; diff --git a/src/team.rs b/src/team.rs index 596798f..da4a34f 100644 --- a/src/team.rs +++ b/src/team.rs @@ -1,4 +1,3 @@ -use std::error::Error; use std::fmt; use image::Rgb; @@ -38,7 +37,8 @@ impl TryFrom<u16> for Team { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)] +#[error("no content of type Team for value {0}")] pub struct TryFromU16Error(pub u16); impl From<Team> for u8 { @@ -62,19 +62,12 @@ impl fmt::Display for Team { 3 => f.write_str("Malis"), 4 => f.write_str("Green"), 5 => f.write_str("Blue"), + 6 => f.write_str("Neoplastic"), id => write!(f, "Team #{id}"), } } } -impl fmt::Display for TryFromU16Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "no content of type Team for value {}", self.0) - } -} - -impl Error for TryFromU16Error {} - const TEAM_NAMES: &str = include_str!("../res/team_names.txt"); impl Content for Team { @@ -94,6 +87,7 @@ impl Content for Team { 3 => "malis", 4 => "green", 5 => "blue", + 6 => "neoplastic", // dark magic: offsets manually computed, then rely on the format "...|team#{i}|..." i @ 6..=9 => { // length: 7 ("team#" (5) + 1 digit + "|" (1)) diff --git a/src/utils/array.rs b/src/utils/array.rs new file mode 100644 index 0000000..6685baa --- /dev/null +++ b/src/utils/array.rs @@ -0,0 +1,63 @@ +use std::{ + fmt::{Debug, Write}, + ops::Deref, +}; +#[derive(Clone, PartialEq, Eq)] +pub struct Array2D<T: Clone> { + width: usize, + height: usize, + /// column + data: Box<[T]>, +} + +impl<T: Debug + Clone> Debug for Array2D<Option<T>> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Arr[\n")?; + for y in (0..self.height).rev() { + for x in 0..self.width { + let t = &self[x][y]; + if let Some(t) = t { + t.fmt(f)?; + } else { + f.write_char('_')?; + } + f.write_str(", ")?; + } + f.write_char('\n')?; + } + f.write_char(']')?; + Ok(()) + } +} + +impl<T: Clone> Array2D<T> { + pub fn new(fill: T, width: usize, height: usize) -> Array2D<T> { + Array2D { + width, + height, + data: vec![fill; width * height].into_boxed_slice(), + } + } +} + +impl<T: Clone> Deref for Array2D<T> { + type Target = Box<[T]>; + /// a sin it is + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<T: Clone> std::ops::Index<usize> for Array2D<T> { + type Output = [T]; + + fn index(&self, x: usize) -> &Self::Output { + &self.data[self.height * x..self.height * (x + 1)] + } +} + +impl<T: Clone> std::ops::IndexMut<usize> for Array2D<T> { + fn index_mut(&mut self, x: usize) -> &mut Self::Output { + &mut self.data[self.height * x..self.height * (x + 1)] + } +} diff --git a/src/utils/image.rs b/src/utils/image.rs index de6cf6b..959b633 100644 --- a/src/utils/image.rs +++ b/src/utils/image.rs @@ -1,17 +1,39 @@ use fast_image_resize as fr; -use image::{Rgb, Rgba, RgbaImage}; +use image::{GenericImageView, Rgb, Rgba, RgbaImage}; use std::num::NonZeroU32; +use blurslice::gaussian_blur_bytes; + pub trait ImageUtils { + /// Tint this image with the color fn tint(&mut self, color: Rgb<u8>) -> &mut Self; - - fn repeat(&mut self, with: &RgbaImage) -> &mut Self; - - fn overlay(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self; - + /// Repeat with over self + fn repeat(&mut self, with: &Self) -> &mut Self; + /// Overlay with onto self (does not blend) + fn overlay(&mut self, with: &Self, x: u32, y: u32) -> &mut Self; + /// rotate + fn rotate(&mut self, times: u8) -> &mut Self; + /// shadow + fn shadow(&mut self) -> &mut Self; + /// silhouette + fn silhouette(&mut self) -> &mut Self; + /// scale a image + /// + /// SAFETY: to and width and height cannot be 0. unsafe fn scale(self, to: u32) -> Self; } impl ImageUtils for RgbaImage { + fn rotate(&mut self, times: u8) -> &mut Self { + use image::imageops::{rotate180, rotate270, rotate90}; + match times { + 2 => *self = rotate180(self), + 1 => *self = rotate90(self), + 3 => *self = rotate270(self), + _ => {} + } + self + } + fn tint(&mut self, color: Rgb<u8>) -> &mut Self { let [tr, tg, tb] = [ color[0] as f32 / 255.0, @@ -39,7 +61,7 @@ impl ImageUtils for RgbaImage { for j in 0..with.height() { for i in 0..with.width() { let get = with.get_pixel(i, j); - if get[3] > 5 { + if get[3] > 128 { self.put_pixel(i + x, j + y, *get); } } @@ -47,9 +69,6 @@ impl ImageUtils for RgbaImage { self } - /// scales a image - /// - /// SAFETY: to and width and height cannot be 0. unsafe fn scale(self, to: u32) -> Self { debug_assert_ne!(to, 0); debug_assert_ne!(self.width(), 0); @@ -68,4 +87,36 @@ impl ImageUtils for RgbaImage { .unwrap(); RgbaImage::from_raw(to.get(), to.get(), dst.into_vec()).unwrap() } + + fn silhouette(&mut self) -> &mut Self { + for pixel in self.pixels_mut() { + if pixel[3] < 128 { + pixel[2] /= 10; + pixel[1] /= 10; + pixel[0] /= 10; + } + } + self + } + + fn shadow(&mut self) -> &mut Self { + let mut shadow = self.clone(); + shadow.silhouette(); + let samples = shadow.as_flat_samples_mut(); + gaussian_blur_bytes::<4>(samples.samples, self.width() as usize, self.height() as usize, 9.0).unwrap(); + for x in 0..shadow.width() { + for y in 0..shadow.height() { + let Rgba([r, g, b, a]) = self.get_pixel_mut(x, y); + if *a == 0 { + // SAFETY: yes + let p = unsafe { shadow.unsafe_get_pixel(x, y) }; + *r = p[0]; + *g = p[0]; + *b = p[0]; + *a = p[1]; + } + } + } + self + } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 4d12b15..b9aa815 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,3 @@ +pub mod array; pub mod image; pub use self::image::ImageUtils; |