Skip to main content

mindus/data/
autotile.rs

1use super::GridPos;
2use super::renderer::*;
3use crate::block::{Block, Rotation, content::Type};
4use bobbin_bits::U4;
5
6#[cfg(test)]
7macro_rules! dir {
8    (^) => {
9        crate::block::Rotation::Up
10    };
11    (v) => {
12        crate::block::Rotation::Down
13    };
14    (<) => {
15        crate::block::Rotation::Left
16    };
17    (>) => {
18        crate::block::Rotation::Right
19    };
20}
21#[cfg(test)]
22macro_rules! conv {
23    (_) => {
24        None
25    };
26    ($dir:tt) => {
27        Some((&crate::block::CONVEYOR, crate::data::autotile::dir!($dir)))
28    };
29}
30#[cfg(test)]
31macro_rules! define {
32    ($a:tt,$b:tt,$c:tt,$d:tt) => {
33        [
34            crate::data::autotile::conv!($a),
35            crate::data::autotile::conv!($b),
36            crate::data::autotile::conv!($c),
37            crate::data::autotile::conv!($d),
38        ]
39    };
40}
41
42#[cfg(test)]
43pub(crate) use conv;
44#[cfg(test)]
45pub(crate) use define;
46#[cfg(test)]
47pub(crate) use dir;
48
49pub type Cross = [Option<(&'static Block, Rotation)>; 4];
50pub type Corners = [Option<(&'static Block, Rotation)>; 4];
51/// holds the 4 bordering blocks
52#[derive(Copy, Clone)]
53pub struct RenderingContext {
54    pub cross: Cross,
55    pub corners: Corners,
56    pub position: PositionContext,
57}
58
59/// holds positions
60#[derive(Copy, Clone, Eq, PartialEq, Default)]
61pub struct PositionContext {
62    pub position: GridPos,
63    pub width: usize,
64    pub height: usize,
65}
66
67impl std::fmt::Debug for PositionContext {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(
70            f,
71            "PC<{:?} ({}/{})>",
72            self.position, self.width, self.height
73        )
74    }
75}
76
77#[cfg(test)]
78fn print_crosses(v: Vec<Cross>, height: usize) -> String {
79    let mut s = String::new();
80    for c in v.chunks(height) {
81        for c in c {
82            s.push(c[0].map_or('_', |(_, r)| r.ch()));
83            for c in &c[1..] {
84                s.push(',');
85                s.push(c.map_or('_', |(_, r)| r.ch()));
86            }
87            s.push(' ');
88        }
89        s.push('\n');
90    }
91    s
92}
93
94pub fn tile(ctx: &RenderingContext, name: &str, rot: Rotation, s: Scale) -> ImageHolder<4> {
95    mask2tile(mask(ctx, rot, name), rot, name, s)
96}
97
98pub fn mask2tile(mask: U4, rot: Rotation, name: &str, scale: Scale) -> ImageHolder<4> {
99    use U4::*;
100    macro_rules! p {
101        ($image:literal) => {
102            load!(concat $image => name which is ["reinforced-conduit" | "armored-duct" | "pulse-conduit" | "plated-conduit" | "conduit" | "conveyor" | "titanium-conveyor" | "armored-conveyor" | "duct"], scale)
103        };
104    }
105
106    match mask {
107        // from left
108        B0001 => match rot {
109            Rotation::Down => p!("1-1-h"), // ┐
110            Rotation::Right => p!("0-0"),  // ─
111            Rotation::Up => p!("1-3"),     // ┘
112            Rotation::Left => unreachable!(),
113        },
114        // from below
115        B0010 => match rot {
116            Rotation::Left => p!("1-2"),    // ┐
117            Rotation::Right => p!("1-2-h"), // ┌
118            Rotation::Up => p!("0-3"),      // │
119            Rotation::Down => unreachable!(),
120        },
121        // from bottom + left
122        B0011 => match rot {
123            Rotation::Right => p!("2-0"), // ┬
124            Rotation::Up => p!("2-3-h"),  // ┤
125            _ => unreachable!(),
126        },
127        // from right
128        B0100 => match rot {
129            Rotation::Left => p!("0-2"), // ─
130            Rotation::Down => p!("1-1"), // ┌
131            Rotation::Up => p!("1-1-v"), // └
132            Rotation::Right => unreachable!(),
133        },
134        // from sides
135        B0101 => match rot {
136            Rotation::Up => p!("4-3"),   // ┴
137            Rotation::Down => p!("4-1"), // ┬
138            _ => unreachable!(),
139        },
140        // from right + down
141        B0110 => match rot {
142            Rotation::Up => p!("2-3"),     // ├,
143            Rotation::Left => p!("2-0-h"), // ┬
144            _ => unreachable!(),
145        },
146        // from right + down + left
147        B0111 => match rot {
148            Rotation::Up => p!("3-3"), // ┼
149            _ => unreachable!(),
150        },
151        // from above
152        B1000 => match rot {
153            Rotation::Down => p!("0-1"),   // │
154            Rotation::Left => p!("1-0-h"), // ┘
155            Rotation::Right => p!("1-0"),  // └
156            Rotation::Up => unreachable!(),
157        },
158        // from top and left
159        B1001 => match rot {
160            Rotation::Right => p!("2-0-v"), // ┴
161            Rotation::Down => p!("2-1"),    // ┤
162            _ => unreachable!(),
163        },
164        // from top sides
165        B1010 => match rot {
166            Rotation::Right => p!("4-0"), // ├
167            Rotation::Left => p!("4-3"),  // ┤
168            _ => unreachable!(),
169        },
170        // from top, left, bottom
171        B1011 => match rot {
172            Rotation::Right => p!("3-0"), // ┼
173            _ => unreachable!(),
174        },
175        // from top and right
176        B1100 => match rot {
177            Rotation::Down => p!("2-1-h"), // ├
178            Rotation::Left => p!("2-2"),   // ┴
179            _ => unreachable!(),
180        },
181        // from top, left, right
182        B1101 => match rot {
183            Rotation::Down => p!("3-1"), // ┼
184            _ => unreachable!(),
185        },
186        // from top, right, bottom
187        B1110 => match rot {
188            Rotation::Left => p!("3-0-h"), // ┼
189            _ => unreachable!(),
190        },
191        B0000 => match rot {
192            Rotation::Left => p!("0-2"),
193            Rotation::Right => p!("0-0"),
194            Rotation::Down => p!("0-1"),
195            Rotation::Up => p!("0-3"),
196        },
197        B1111 => unreachable!(),
198    }
199}
200
201pub fn mask(ctx: &RenderingContext, rot: Rotation, n: &str) -> U4 {
202    macro_rules! c {
203        ($in: expr, $srot: expr, $name: expr, $at: expr) => {{
204            if let Some((b, rot)) = $in {
205                if b.name() == $name {
206                    // if they go down, we must not go up
207                    (rot == $at && rot.mirrored(true, true) != $srot) as u8
208                } else {
209                    0
210                }
211            } else {
212                0
213            }
214        }};
215    }
216    use Rotation::{Down, Left, Right, Up};
217    let mut x = 0b0000;
218
219    x |= 8 * c!(ctx.cross[0], rot, n, Down);
220    x |= 4 * c!(ctx.cross[1], rot, n, Left);
221    x |= 2 * c!(ctx.cross[2], rot, n, Up);
222    x |= c!(ctx.cross[3], rot, n, Right);
223    U4::from(x)
224}
225
226pub fn nbors(corners: [Option<Type>; 4], cross: [Option<Type>; 4], t: Type) -> u8 {
227    let x = [
228        cross[1], corners[3], cross[0], corners[2], cross[3], corners[0], cross[2], corners[1],
229    ];
230
231    use std::simd::prelude::*;
232    u8x8::from_array(x.map(|x| (x == Some(t)) as u8))
233        .simd_eq(u8x8::splat(1))
234        .to_bitmask() as u8
235}
236
237macro_rules! g {
238    ($name:literal, $($i:literal,)+) => { paste::paste! { [
239        $(load!([<$name _ $i>]),)+
240    ] }};
241}
242
243pub fn select(n: &str, mask: u8) -> [Image<&'static [u8], 4>; 3] {
244    macro_rules! autotiled {
245    ($($name:literal)+) => {{ paste::paste! { $(const [<$name:snake:upper>]: [[Image<&[u8], 4>; 3]; 256] = g![$name,
24639, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
24738, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
24839, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
24938, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
250 3,  4,  3,  4, 15, 40, 15, 20,  3,  4,  3,  4, 15, 40, 15, 20,
251 5, 28,  5, 28, 29, 10, 29, 23,  5, 28,  5, 28, 31, 11, 31, 32,
252 3,  4,  3,  4, 15, 40, 15, 20,  3,  4,  3,  4, 15, 40, 15, 20,
253 2, 30,  2, 30,  9, 46,  9, 22,  2, 30,  2, 30, 14, 44, 14,  6,
25439, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
25538, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
25639, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
25738, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
258 3,  0,  3,  0, 15, 42, 15, 12,  3,  0,  3,  0, 15, 42, 15, 12,
259 5,  8,  5,  8, 29, 35, 29, 33,  5,  8,  5,  8, 31, 34, 31,  7,
260 3,  0,  3,  0, 15, 42, 15, 12,  3,  0,  3,  0, 15, 42, 15, 12,
261 2,  1,  2,  1,  9, 45,  9, 19,  2,  1,  2,  1, 14, 18, 14, 13,];)+ match n {
262    $($name => { [<$name:snake:upper>][mask as usize] },)+
263    x => unreachable!("{x}")
264 }}}};
265}
266    autotiled!("colored-floor" "colored-wall"
267               "metal-tiles-1" "metal-tiles-2" "metal-tiles-3" "metal-tiles-4" "metal-tiles-5""metal-tiles-6" "metal-tiles-7" "metal-tiles-8" "metal-tiles-9" "metal-tiles-10" "metal-tiles-11" "metal-tiles-12" "metal-tiles-13"
268               "metal-wall-1" "metal-wall-2" "metal-wall-3"
269    )
270}
271
272pub trait RotationState {
273    fn get_rotation(&self) -> Option<Rotation>;
274}
275pub trait BlockState {
276    fn get_block(&self) -> Option<&'static Block>;
277}
278
279#[test]
280fn test_cross() {
281    macro_rules! test {
282        ($schem: literal => $($a:tt,$b:tt,$c:tt,$d:tt)*) => {
283            let s = crate::Schematic::deserialize_base64($schem).unwrap();
284            let mut c = vec![];
285            println!("{:#?}", s.blocks);
286            for (position, _) in s.block_iter() {
287                let pctx = PositionContext {
288                    position,
289                    width: s.width,
290                    height: s.height,
291                };
292                c.push(s.cross(&pctx).0);
293            }
294            let n = s.tags.get("name").map_or("<unknown>", |x| &x);
295            let cc: Vec<Cross> = vec![
296                $(define!($a,$b,$c,$d),)*
297            ];
298            if cc != c {
299                let a = print_crosses(cc, s.height as usize);
300                let b = print_crosses(c, s.height as usize);
301                for diff in diff::lines(&a, &b) {
302                    match diff {
303                        diff::Result::Left(l)    => println!("\x1b[38;5;1m{}", l),
304                        diff::Result::Right(r)   => println!("\x1b[38;5;2m{}", r),
305                        diff::Result::Both(l, _) => println!("\x1b[0m{}", l),
306                    }
307                }
308                print!("\x1b[0m");
309                /*
310                for diff in diff::slice(&c.into_iter().enumerate().collect::<Vec<_>>(), &cc.into_iter().enumerate().collect::<Vec<_>>()) {
311                    match diff {
312                        diff::Result::Left((i, l))    => println!("\x1b[38;5;1m- {l:?} at {i}"),
313                        diff::Result::Right((i, r))   => println!("\x1b[38;5;2m+ {r:?} at {i}"),
314                        diff::Result::Both((i, l), _) => println!("\x1b[0m  {l:?} at {i}"),
315                    }
316                }
317                */
318                panic!("test {n} \x1b[38;5;1mfailed\x1b[0m")
319            }
320            println!("test {n} \x1b[38;5;2mpassed\x1b[0m");
321        };
322    }
323    // crosses go from bottom left -> top left -> bottom left + 1 -> top left + 1...
324    // the symbols are directions (> => Right...), which mean the neighbors pointing direction
325    // _ = no block
326
327    // the basic test
328    // ─┐
329    // ─┤
330    test!("bXNjaAF4nGNgYmBiZmDJS8xNZWBNSizOTGbgTkktTi7KLCjJzM9jYGBgy0lMSs0pZmCNfr9gTSwjA0dyfl5ZamV+EVCOhQEBGGEEM4hiZGAGAOb+EWA=" =>
331    //  (0, 0)  (0, 1)
332    //  n e s w borders (west void for first row)
333        >,v,_,_ _,v,>,_
334    //  (1, 0)  (1, 1)
335        v,_,_,> _,_,v,>
336    );
337    // the loop test
338    // ─│─
339    // ─┼┐
340    // ─└┘
341    test!("bXNjaAF4nDWK4QqAIBCDd6dE0SNGP8zuh2CeaAS9fZk0xvjGBgNjYJM7BDaqy5h3qb6EfAZNAIboNokVvKyE0Wu65NbyDhM+cQv6mTtTM/WFYfqLm6m3lx9MAg7n" =>
342        >,^,_,_ <,>,<,_ _,v,>,_
343        >,<,_,< v,v,^,> _,>,>,<
344        v,_,_,^ >,_,<,> _,_,v,v
345    );
346    // the snek test
347    // └┐
348    // ─┘
349    test!("bXNjaAF4nGNgYmBiZmDJS8xNZWApzkvNZuBOSS1OLsosKMnMz2NgYGDLSUxKzSlmYIqOZWTgSM7PK0utzC8CSrAwIAAjEIIQhGJkYAIARA0Ozg==" =>
350        ^,^,_,_ _,<,>,_
351        <,_,_,> _,_,^,^
352    );
353
354    // the notile test
355    test!("bXNjaAF4nCWJQQqAIBREx69E0Lq994oWph8STEMj6fZpzcDjDQMCSahoDsZsdN1TYB25aucz28uniMlxsdmf3wCGYDYOBbSsAqNN8eYn5XYofJEdAtSB31tfaoIVGw==" =>
356        <,>,_,_ _,^,v,_
357        ^,_,_,v _,_,>,<
358    );
359    // the asymmetrical test
360    // <───
361    // ───>
362    test!("bXNjaAF4nEXJwQqAIBAE0HGVCPrE6GC2B0HdcCPw78MKnMMwj4EFWbjiM8N5bRnLwRpqPK8oBcCU/M5JQetmMAcpNzep/cCIAfX69yv6RF0PFy0O4Q==" =>
363        <,>,_,_ _,<,>,_
364        <,>,_,> _,<,>,<
365        // <,_,_,> _,_,>,<
366        <,_,_,> _,_,>,<
367    );
368
369    // the complex test
370    // ─┬─│││─
371    // ─┤─┘─┘─
372    // ─┤┌─│─┐
373    // ─┼┘─┴─│
374    test!("bXNjaAF4nEWOUQ7CIBBEh2VZTbyCx/A2xg9a+WiC0LTGxNvb7Wjk5wEzb7M4QCO05UdBqj3PF5zuZR2XaX5OvQGwmodSV8j1FnAce3uVd1+24Iz/CYQQ8fcVHYEQIjqEXWEm9LwgX9kR+PLSbm2BMlN6Sk/3LhJnJu6S6CVmxl2MntEzv38AchUPug==" =>
375        >,v,_,_ >,v,>,_ >,v,>,_ _,v,>,_
376        v,<,_,> v,v,v,> v,>,v,> _,<,v,>
377        v,>,_,v >,<,<,v <,^,v,v _,v,>,v
378        <,>,_,< ^,v,>,v v,>,<,> _,^,^,<
379        v,<,_,> >,>,>,< ^,^,v,^ _,^,>,v
380        >,v,_,> ^,v,<,v ^,>,>,> _,>,^,^
381        // <,_,_,< ^,_,>,v v,_,<,> _,_,^,<
382        // v,_,_,> >,_,>,< ^,_,v,^ _,_,>,v
383        // >,_,_,> ^,_,<,v ^,_,>,> _,_,^,^
384        v,_,_,< >,_,v,> >,_,v,^ _,_,>,^
385    );
386}
387
388#[test]
389fn test_mask() {
390    macro_rules! assert {
391        ($a:tt,$b:tt,$c:tt,$d:tt => $rot: tt => $expect: expr) => {
392            assert_eq!(mask!(define!($a, $b, $c, $d), $rot), $expect)
393        };
394    }
395    macro_rules! mask {
396        ($cross:expr, $rot: tt) => {
397            mask(
398                &RenderingContext {
399                    position: PositionContext {
400                        position: GridPos(5, 5),
401                        width: 10,
402                        height: 10,
403                    },
404                    corners: Default::default(),
405                    cross: $cross,
406                },
407                dir!($rot),
408                "conveyor",
409            )
410        };
411    }
412    assert!(_,_,_,_ => ^ => U4::B0000);
413    assert!(v,_,_,_ => > => U4::B1000);
414    assert!(v,v,_,_ => v => U4::B1000);
415    assert!(_,v,>,_ => > => U4::B0000);
416    assert!(v,>,<,> => ^ => U4::B0001);
417    assert!(v,>,>,_ => > => U4::B1000);
418}