mindus/data/
renderer.rs

1//! schematic drawing
2use super::GridPos;
3pub(crate) use super::autotile::*;
4use super::schematic::Schematic;
5use crate::block::State;
6use crate::block::content::Type;
7use crate::color_mapping::BLOCK2COLOR;
8use crate::data::map::Registrar;
9use crate::team::Team;
10pub(crate) use crate::utils::*;
11use crate::{Map, content::Content};
12use crate::{
13    block::Rotation,
14    data::map::{ThinBloc, ThinMapData},
15};
16use atools::prelude::*;
17use either::Either;
18use fimg::{BlendingOverlay, BlendingOverlayAt, uninit};
19use std::hint::unlikely;
20use std::iter::successors;
21use std::ops::Coroutine;
22use std::pin::Pin;
23
24include!(concat!(env!("OUT_DIR"), "/full.rs"));
25include!(concat!(env!("OUT_DIR"), "/quar.rs"));
26include!(concat!(env!("OUT_DIR"), "/eigh.rs"));
27
28#[derive(Debug, Copy, Clone)]
29#[repr(u8)]
30pub enum Scale {
31    Full,
32    // Half,
33    Quarter,
34    Eigth,
35}
36
37impl Scale {
38    #[must_use]
39    pub const fn px(self) -> u8 {
40        match self {
41            Self::Full => 32,
42            Self::Quarter => 32 / 4,
43            Self::Eigth => 32 / 8,
44        }
45    }
46}
47
48impl std::ops::Mul<u32> for Scale {
49    type Output = u32;
50    fn mul(self, rhs: u32) -> u32 {
51        self.px() as u32 * rhs
52    }
53}
54
55#[macro_export]
56macro_rules! load {
57	(raw $name: literal, $scale:expr) => {
58		paste::paste! { match $scale {
59            $crate::data::renderer::Scale::Quarter => $crate::data::renderer::quar::[<$name:snake:upper>],
60            $crate::data::renderer::Scale::Eigth => $crate::data::renderer::eigh::[<$name:snake:upper>],
61            $crate::data::renderer::Scale::Full => $crate::data::renderer::full::[<$name:snake:upper>],
62        }
63    } };
64    (8x $name:literal) => { paste::paste! {
65        car::map!([
66            load!([<$name 1>]),
67            load!([<$name 2>]),
68            load!([<$name 3>]),
69            load!([<$name 4>]),
70            load!([<$name 5>]),
71            load!([<$name 6>]),
72            load!([<$name 7>]),
73            load!([<$name 8>]),
74        ], |x| car::map!(x, DynImage::from))
75    } };
76    ($name:literal, $scale:expr) => { paste::paste! {
77        #[allow(unused_unsafe)] unsafe { match $scale {
78            $crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::[<$name:snake:upper>],
79            $crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::[<$name:snake:upper>],
80            $crate::data::renderer::Scale::Full => &$crate::data::renderer::full::[<$name:snake:upper>],
81        }.mapped($crate::utils::Cow::Ref) }
82    } };
83    ($name: ident) => { paste::paste! {
84        [$crate::data::renderer::full::[<$name:snake:upper>].copy(), $crate::data::renderer::quar::[<$name:snake:upper>].copy(), $crate::data::renderer::eigh::[<$name:snake:upper>].copy()]
85    } };
86    ($name: literal) => { paste::paste! {
87        [$crate::data::renderer::full::[<$name:snake:upper>].copy(), $crate::data::renderer::quar::[<$name:snake:upper>].copy(), $crate::data::renderer::eigh::[<$name:snake:upper>].copy()]
88    } };
89    (from $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => {
90        $crate::data::renderer::load!($scale -> match $v {
91            $($k => $k,)+
92        })
93    };
94    // turn load!(s -> match x { "v" => "y" }) into match x { "v" => load!("y", s) }
95    ($scale:ident -> match $v:ident { $($k:pat => $nam:literal $(,)?)+ }) => {
96        match $v {
97            $($k => $crate::data::renderer::load!($nam, $scale),)+
98            #[allow(unreachable_patterns)]
99            n => unreachable!("{n:?}"),
100        }
101    };
102    (concat $x:literal => $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => { paste::paste! {
103        match $v {
104            $($k =>
105                #[allow(unused_unsafe)] unsafe { (match $scale {
106                    $crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::[<$k:snake:upper _ $x:snake:upper>],
107                    $crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::[<$k:snake:upper _ $x:snake:upper>],
108                    $crate::data::renderer::Scale::Full => &$crate::data::renderer::full::[<$k:snake:upper _ $x:snake:upper>],
109                }.mapped($crate::utils::Cow::Ref)) },
110            )+
111            #[allow(unreachable_patterns)]
112            n => unreachable!("{n:?}"),
113        }
114    } };
115}
116pub(crate) use load;
117
118/// trait for renderable objects
119pub trait Renderable {
120    /// create a picture
121    #[must_use = "i did so much work for you"]
122    fn render(&self) -> Image<Vec<u8>, 3>;
123}
124
125impl Renderable for Schematic {
126    /// creates a picture of a schematic. Bridges and node connections are not drawn.
127    /// ```
128    /// # use mindus::*;
129    /// # use mindus::block::*;
130    /// let mut s = Schematic::new(2, 3);
131    /// s.put(0, 0, &DISTRIBUTOR);
132    /// s.put(0, 2, &ROUTER);
133    /// s.put(1, 2, &COPPER_WALL);
134    /// let output /*: Image */ = s.render();
135    /// ```
136    fn render(&self) -> Image<Vec<u8>, 3> {
137        let scale = if self.width + self.height > 500 {
138            Scale::Quarter
139        } else {
140            Scale::Full
141        };
142        // fill background
143        // SAFETY: metal-floor is scalexscale, the output is a multiple of scale
144        let x_fac = cfg!(feature = "square") as u32
145            * self.height.checked_sub(self.width).unwrap_or(0) as u32
146            + 2;
147        let y_fac = cfg!(feature = "square") as u32
148            * self.width.checked_sub(self.height).unwrap_or(0) as u32
149            + 2;
150        let mut bg = unsafe {
151            load!("metal-floor", scale).repeated(
152                scale * (self.width + x_fac as usize) as u32,
153                scale * (self.height + y_fac as usize) as u32,
154            )
155        };
156        let mut canvas = Image::alloc(bg.width(), bg.height());
157        for (GridPos(x, y), tile) in self.block_iter() {
158            let ctx = tile.block.wants_context().then(|| {
159                let pctx = PositionContext {
160                    position: GridPos(x, y),
161                    width: self.width,
162                    height: self.height,
163                };
164                let (cross, corners) = self.cross(&pctx);
165                RenderingContext {
166                    cross,
167                    corners,
168                    position: pctx,
169                }
170            });
171            let x = x as u32 - ((tile.block.get_size() - 1) / 2) as u32;
172            let y = self.height as u32 - y as u32 - ((tile.block.get_size() / 2) + 1) as u32;
173            unsafe {
174                canvas.as_mut().overlay_at(
175                    &tile.image(
176                        ctx.as_ref(),
177                        tile.get_rotation().unwrap_or(Rotation::Up),
178                        scale,
179                    ),
180                    scale * (x + x_fac / 2),
181                    scale * (y + y_fac / 2),
182                )
183            };
184        }
185        for (p, b) in self.block_iter() {
186            let Some(&State::Point(mut relative)) = b.get_state() else {
187                continue;
188            };
189            let directional = matches!(b.block.name(), "duct-bridge" | "reinforced-bridge-conduit");
190            if false {
191                let offset = match b.rot {
192                    Rotation::Right => (1, 0),
193                    Rotation::Down => (0, -1),
194                    Rotation::Left => (-1, 0),
195                    Rotation::Up => (0, 1),
196                };
197                relative = successors(Some(offset), |&(x, y)| Some((x + offset.0, y + offset.1)))
198                    .take(4)
199                    .find(|x| {
200                        self.get((p.0 as i32 + x.0) as _, (p.1 as i32 + x.1) as _)
201                            .ok()
202                            .flatten()
203                            .is_some_and(|x| x.block == b.block)
204                    });
205            }
206            if let Some(relative) = relative
207                && relative != (0, 0)
208                && let n @ ("bridge-conveyor" | "bridge-conduit" | "phase-conveyor"
209                | "phase-conduit") = b.block.name()
210                && let Ok(Some(x)) = self.get(
211                    (p.0 as i32 + relative.0) as _,
212                    (p.1 as i32 + relative.1) as _,
213                )
214                && x.block.name() == n
215            {
216                let mut bridge = load!(concat "bridge" => n which is ["bridge-conveyor" | "bridge-conduit" | "phase-conveyor" | "phase-conduit" | "duct-bridge" | "reinforced-bridge-conduit"], scale);
217
218                /*
219                let mut bridge = bridge.own();
220                bridge.chunked_mut().for_each(|x| match &mut x[3] {
221                    0 => (),
222                    y => *y = (*y as f32 * 0.20) as u8,
223                });
224                bridge.save(format!("{n}-bridge.png"));
225                let mut bridge = ImageHolder::from(bridge);
226                */
227                if relative.1 != 0 {
228                    // continue;
229                    bridge =
230                        Image::build(bridge.height(), bridge.width()).buf(bridge.take_buffer());
231                }
232                let arrow = load!(concat "arrow" => n which is ["bridge-conveyor"| "bridge-conduit" | "phase-conveyor" | "phase-conduit" | "duct-bridge" | "reinforced-bridge-conduit"], scale);
233                /*
234                let mut arrow = arrow.own();
235                arrow.chunked_mut().for_each(|x| match &mut x[3] {
236                    0 => (),
237                    y => *y = (*y as f32 * 0.20) as u8,
238                });
239                arrow.save(format!("{n}-arrow.png"));
240                let mut arrow = ImageHolder::from(arrow);
241                */
242
243                for index in if relative.0 > 0 {
244                    Either::Right(
245                        scale.px() as i32 * 2..scale.px() as i32 * relative.0 + scale.px() as i32,
246                    )
247                } else {
248                    Either::Left(
249                        relative.0 * scale.px() as i32 + scale.px() as i32 * 2..scale.px() as i32,
250                    )
251                } {
252                    unsafe {
253                        canvas.overlay_blended_at(
254                            &bridge,
255                            (p.0 as i32 * scale.px() as i32 + index) as u32,
256                            scale * (self.height as u32 - p.1 as u32 - 1 + y_fac / 2),
257                        )
258                    };
259                }
260
261                for index in if relative.1 > 0 {
262                    Either::Right(
263                        -(scale.px() as i32) - scale.px() as i32 * (relative.1 - 1)
264                            ..-(scale.px() as i32),
265                    )
266                } else {
267                    Either::Left(0..(-relative.1 - 1) * scale.px() as i32)
268                } {
269                    let y =
270                        ((self.height as i32 - p.1 as i32 + 1) * scale.px() as i32 + index) as u32;
271
272                    unsafe {
273                        canvas.overlay_blended_at(&bridge, scale * (p.0 as u32 + x_fac / 2), y)
274                    };
275                }
276                if relative.0 != 0 {
277                    unsafe {
278                        canvas.overlay_blended_at(
279                            &arrow.rotated(if directional {
280                                b.rot.rotated(false).count()
281                            } else if relative.0 < 0 {
282                                2
283                            } else {
284                                0
285                            }),
286                            scale.px() as u32
287                                + ((scale * p.0 as u32).cast_signed()
288                                    + (scale.px() as i32 * relative.0 / 2))
289                                    as u32,
290                            (y_fac / 2 + scale * (self.height as u32 - p.1 as u32)) - 1,
291                        )
292                    };
293                } else {
294                    unsafe {
295                        canvas.overlay_blended_at(
296                            &arrow.rotated(if directional {
297                                b.rot.rotated(false).count()
298                            } else if relative.1 < 0 {
299                                1
300                            } else {
301                                3
302                            }),
303                            (scale * p.0 as u32) + (x_fac / 2 + scale.px() as u32) - 1,
304                            scale.px() as u32
305                                + ((scale * (self.height as u32 - p.1 as u32 - 1)).cast_signed()
306                                    + (scale.px() as i32 * -relative.1 / 2))
307                                    as u32,
308                        );
309                    }
310                }
311            }
312        }
313
314        if matches!(scale, Scale::Full) {
315            ImageUtils::shadow(&mut canvas);
316            unsafe { bg.overlay_blended(&canvas) };
317        } else {
318            unsafe { bg.overlay(&canvas) };
319        }
320        bg
321    }
322}
323
324impl Renderable for Map {
325    /// Draws a map
326    #[implicit_fn::implicit_fn]
327    fn render(&self) -> Image<Vec<u8>, 3> {
328        let scale = if self.width + self.height < 600 {
329            Scale::Full
330        } else if self.width + self.height < 4000 {
331            Scale::Quarter
332        } else {
333            Scale::Eigth
334        };
335        use crate::data::map::table;
336        let mut img = uninit::Image::<_, 3>::new(
337            (scale * self.width as u32).try_into().unwrap(),
338            (scale * self.height as u32).try_into().unwrap(),
339        );
340        // loop1 draws the floor
341        for y in 0..self.height {
342            for x in 0..self.width {
343                // Map::new() allocates w*h items
344                let j = x + self.width * y;
345                let tile = unsafe { self.tiles.get_unchecked(j) };
346                let y = self.height - y - 1;
347                // println!("draw {tile:?} ({x}, {y})");
348
349                if [
350                    Type::ColoredFloor,
351                    Type::MetalTiles1,
352                    Type::MetalTiles2,
353                    Type::MetalTiles3,
354                    Type::MetalTiles4,
355                    Type::MetalTiles5,
356                    Type::MetalTiles6,
357                    Type::MetalTiles7,
358                    Type::MetalTiles8,
359                    Type::MetalTiles9,
360                    Type::MetalTiles10,
361                    Type::MetalTiles11,
362                    Type::MetalTiles12,
363                ]
364                .contains(&tile.floor)
365                {
366                    let mask = crate::data::autotile::nbors(
367                        self.corners(j).map(_.map(_.floor)),
368                        self.cross(j).map(_.map(_.floor)),
369                        tile.floor,
370                    );
371                    let i =
372                        &crate::data::autotile::select(tile.floor.get_name(), mask)[scale as usize];
373                    if tile.floor == Type::ColoredFloor {
374                        let mut i = i.boxed();
375                        unsafe {
376                            img.overlay_at(
377                                &i.as_mut()
378                                    .tint(tile.nd.skip::<3>().take::<3>().into())
379                                    .as_ref(),
380                                scale * x as u32,
381                                scale * y as u32,
382                            )
383                        };
384                    } else {
385                        unsafe { img.overlay_at(i, scale * x as u32, scale * y as u32) };
386                    }
387                } else {
388                    unsafe {
389                        img.overlay_at(
390                            &table(tile.floor, scale),
391                            scale * x as u32,
392                            scale * y as u32,
393                        );
394                    }
395                }
396                macro_rules! f {
397                    ($($x: literal)+) => { paste::paste!{
398                        ([$(load!([<rune _ overlay $x>] ),)+], [$(load!([<rune _ overlay _ crux $x>] ),)+])
399                    }};
400                }
401                const RUNES: ([[Image<&[u8], 4>; 3]; 109], [[Image<&[u8], 4>; 3]; 109]) = f![0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108];
402
403                if tile.has_ore() {
404                    match tile.ore {
405                        Type::RuneOverlay => unsafe {
406                            img.overlay_at(
407                                &RUNES.0[tile.nd[2] as usize][scale as usize],
408                                scale * x as u32,
409                                scale * y as u32,
410                            );
411                        },
412                        Type::RuneOverlayCrux => unsafe {
413                            img.overlay_at(
414                                &RUNES.1[tile.nd[2] as usize][scale as usize],
415                                scale * x as u32,
416                                scale * y as u32,
417                            );
418                        },
419                        Type::CharacterOverlay | Type::CharacterOverlayWhite => {
420                            macro_rules! f {
421                                ($($x: literal)+) => { paste::paste!{
422                                    [$(load!([<character _ overlay $x>] ),)+]
423                                }};
424                            }
425
426                            const LETTERS: [[Image<&[u8], 4>; 3]; 64] = f![0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63];
427                            unsafe {
428                                img.overlay_at(
429                                    LETTERS[(tile.nd[2] & 0x3f) as usize][scale as usize]
430                                        .mapped(image::Cow::Ref)
431                                        .rotate(4 - (tile.nd[2] >> 6)),
432                                    scale * x as u32,
433                                    scale * y as u32,
434                                )
435                            };
436                        }
437                        ore => unsafe {
438                            img.overlay_at(&table(ore, scale), scale * x as u32, scale * y as u32);
439                        },
440                    }
441                }
442            }
443        }
444        let mut img = unsafe { img.assume_init() };
445        // loop2 draws the buildings
446        for y in 0..self.height {
447            for x in 0..self.width {
448                let j = x + self.width * y;
449                let tile = unsafe { self.tiles.get_unchecked(j) };
450                let y = self.height - y - 1;
451                if let Some(build) = tile.build() {
452                    let s = build.block.get_size();
453                    let x = x
454                        - (match s {
455                            1 | 2 => 0,
456                            3 | 4 => 1,
457                            5 | 6 => 2,
458                            7 | 8 => 3,
459                            9 => 4,
460                            // SAFETY: no block too big
461                            _ => unsafe { std::hint::unreachable_unchecked() },
462                        }) as usize;
463                    let y = y
464                        - (match s {
465                            1 => 0,
466                            2 | 3 => 1,
467                            4 | 5 => 2,
468                            6 | 7 => 3,
469                            8 | 9 => 4,
470                            // SAFETY: no block too big
471                            _ => unsafe { std::hint::unreachable_unchecked() },
472                        }) as usize;
473                    if unlikely(matches!(
474                        build.block.name,
475                        Type::ColoredWall | Type::MetalWall1 | Type::MetalWall2 | Type::MetalWall3
476                    )) {
477                        let mask = crate::data::autotile::nbors(
478                            self.corners(j)
479                                .map(|x| x.and_then(|x| x.build().map(|b| b.block.name))),
480                            self.cross(j)
481                                .map(|x| x.and_then(|x| x.build().map(|b| b.block.name))),
482                            build.block.name,
483                        );
484                        let i =
485                            crate::data::autotile::select(build.block.name(), mask)[scale as usize];
486                        if build.block.name == Type::ColoredWall {
487                            unsafe {
488                                img.overlay_at(
489                                    &i.boxed()
490                                        .as_mut()
491                                        .tint(tile.nd.skip::<3>().take::<3>().into())
492                                        .as_ref(),
493                                    scale * x as u32,
494                                    scale * y as u32,
495                                )
496                            };
497                        } else {
498                            unsafe { img.overlay_at(&i, scale * x as u32, scale * y as u32) };
499                        }
500                        continue;
501                    }
502                    let ctx = build.block.wants_context().then(|| {
503                        let pctx = PositionContext {
504                            position: GridPos(x, y),
505                            width: self.width,
506                            height: self.height,
507                        };
508                        RenderingContext {
509                            cross: self
510                                .cross(j)
511                                .map(|b| b.and_then(|b| Some((b.get_block()?, b.get_rotation()?)))),
512                            corners: Default::default(),
513                            position: pctx,
514                        }
515                    });
516                    unsafe {
517                        img.as_mut().overlay_at(
518                            &tile.build_image(ctx.as_ref(), scale),
519                            scale * x as u32,
520                            scale * y as u32,
521                        )
522                    };
523                }
524            }
525        }
526        // loop3 draws the units
527        for entity in &self.entities {
528            // bounds checks
529            let (x, y) = (
530                entity.state.position.0 as u32,
531                self.height as u32 - entity.state.position.1 as u32 - 1,
532            );
533            if x < 10 || x as usize > self.width - 10 || y < 10 || y as usize > self.height - 10 {
534                continue;
535            }
536            unsafe {
537                img.as_mut()
538                    .overlay_at(&entity.draw(scale), scale * x, scale * y)
539            };
540        }
541        img
542    }
543}
544
545#[test]
546fn all_blocks() {
547    use crate::block::content::Type;
548    use crate::content::Content;
549    for t in 19..Type::WorldMessage as u16 {
550        let t = Type::try_from(t).unwrap();
551        if matches!(t, |Type::Empty| Type::SlagCentrifuge
552            | Type::HeatReactor
553            | Type::LegacyMechPad
554            | Type::LegacyUnitFactory
555            | Type::LegacyUnitFactoryAir
556            | Type::LegacyUnitFactoryGround
557            | Type::CommandCenter)
558        {
559            continue;
560        }
561        let name = t.get_name();
562        let t = crate::block::BLOCK_REGISTRY.get(name).unwrap();
563        let _ = t.image(
564            None,
565            Some(&RenderingContext {
566                corners: [None; 4],
567                cross: [None; 4],
568                position: PositionContext {
569                    position: GridPos(0, 0),
570                    width: 5,
571                    height: 5,
572                },
573            }),
574            Rotation::Up,
575            Scale::Quarter,
576        );
577    }
578}
579
580pub fn draw_units(
581    map: &mut crate::data::map::MapReader,
582    mut img: Image<&mut [u8], 3>,
583    size: (u16, u16),
584) -> Result<(), super::map::ReadError> {
585    use std::ops::CoroutineState::*;
586    let scale = if size.0 + size.1 < 2000 {
587        Scale::Quarter
588    } else {
589        Scale::Eigth
590    };
591
592    let mut co = map.entities()?;
593    let n = match Pin::new(&mut co).resume(()) {
594        Yielded(crate::data::map::EntityData::Length(x)) => x,
595        Complete(Err(e)) => return Err(e),
596        _ => unreachable!(),
597    };
598    'out: {
599        for _ in 0..n {
600            match Pin::new(&mut co).resume(()) {
601                Yielded(crate::data::map::EntityData::Data(entity)) => {
602                    // bounds checks
603                    let (x, y) = (
604                        entity.state.position.0 as u32,
605                        size.1 as u32 - entity.state.position.1 as u32 - 1,
606                    );
607                    if x < 10
608                        || x as usize > size.0 as usize - 10
609                        || y < 10
610                        || y as usize > size.1 as usize - 10
611                    {
612                        continue;
613                    }
614                    unsafe {
615                        img.as_mut()
616                            .overlay_at(&entity.draw(scale), scale * x, scale * y)
617                    };
618                }
619                Complete(Err(e)) => return Err(e),
620                Complete(Ok(())) => break 'out,
621                x => unreachable!("{x:?}"),
622            }
623        }
624        match Pin::new(&mut co).resume(()) {
625            Complete(Ok(())) => (),
626            _ => unreachable!(),
627        };
628    }
629    Ok(())
630}
631
632/// Draws a map in a single pass.
633/// This is quite fast, but, unfortunately, has no memory, so conveyors will be drawing imporperly. As will sorters.
634///
635/// Reader must have read to the map section.
636/// Will walk through the map section. use [`draw_units`] after, if you like.
637pub fn draw_map_single(
638    map: &mut crate::data::map::MapReader,
639    r: Registrar,
640) -> Result<(Image<Box<[u8]>, 3>, (u16, u16)), super::map::ReadError> {
641    use std::ops::CoroutineState::*;
642    let mut co = map.thin_map(r)?;
643    let (w, h) = match Pin::new(&mut co).resume(()) {
644        Yielded(ThinMapData::Init { width, height }) => (width, height),
645        Complete(Err(x)) => return Err(x),
646        _ => unreachable!(),
647    };
648    let scale = if w + h < 2000 {
649        Scale::Quarter
650    } else {
651        Scale::Eigth
652    };
653    let mut img = uninit::Image::<_, 3>::new(
654        (scale * w as u32).try_into().unwrap(),
655        (scale * h as u32).try_into().unwrap(),
656    );
657    use crate::data::map::table;
658    // loop1 draws the floor
659    for y in 0..h {
660        for x in 0..w {
661            let (floor, ore) = match Pin::new(&mut co).resume(()) {
662                Yielded(ThinMapData::Tile { floor, ore }) => (floor, ore),
663                Complete(Err(x)) => return Err(x),
664                _ => unreachable!(),
665            };
666            let y = h - y - 1;
667            // println!("draw tile {floor} {ore} @ {x} {y}");
668            unsafe { img.overlay_at(&table(floor, scale), scale * x as u32, scale * y as u32) };
669            if ore != Type::Air {
670                unsafe {
671                    img.overlay_at(&table(ore, scale), scale * x as u32, scale * y as u32);
672                }
673            }
674        }
675    }
676    let mut img = unsafe { img.assume_init() }.boxed();
677    let mut i = 0;
678    while i < (w as usize * h as usize) {
679        let mut draw = |i, r, b: &'static crate::block::Block| {
680            let x = i % w as usize;
681            let y = i / w as usize;
682            let y = h as usize - y - 1;
683            let s = b.get_size();
684            let x = x
685                - (match s {
686                    1 | 2 => 0,
687                    3 | 4 => 1,
688                    5 | 6 => 2,
689                    7 | 8 => 3,
690                    9 => 4,
691                    // SAFETY: no block too big
692                    _ => unsafe { std::hint::unreachable_unchecked() },
693                }) as usize;
694            let y = y
695                - (match s {
696                    1 => 0,
697                    2 | 3 => 1,
698                    4 | 5 => 2,
699                    6 | 7 => 3,
700                    8 | 9 => 4,
701                    // SAFETY: no block too big
702                    _ => unsafe { std::hint::unreachable_unchecked() },
703                }) as usize;
704            let ctx = b.wants_context().then(|| {
705                let pctx = PositionContext {
706                    position: GridPos(x, y),
707                    width: w as usize,
708                    height: h as usize,
709                };
710                RenderingContext {
711                    corners: [None; 4],
712                    cross: [None; 4], // woe
713                    position: pctx,
714                }
715            });
716            unsafe {
717                img.as_mut().overlay_at(
718                    &b.image(None, ctx.as_ref(), r, scale),
719                    scale * x as u32,
720                    scale * y as u32,
721                )
722            };
723        };
724        match Pin::new(&mut co).resume(()) {
725            Yielded(ThinMapData::Bloc(ThinBloc::Many(None, n))) => {
726                i += n as usize;
727            }
728            Yielded(ThinMapData::Bloc(ThinBloc::Build(r, bloc, _))) => {
729                draw(i, r, bloc);
730            }
731            Yielded(ThinMapData::Bloc(ThinBloc::Many(Some(bloc), n))) => {
732                for i in i..=i + n as usize {
733                    draw(i, Rotation::Up, bloc);
734                }
735                i += n as usize;
736            }
737            Complete(Err(x)) => return Err(x),
738            x => unreachable!("{x:?}"),
739        }
740        i += 1;
741    }
742    match Pin::new(&mut co).resume(()) {
743        Complete(Ok(())) => (),
744        f => unreachable!("{f:?}"),
745    };
746
747    Ok((img, (w, h)))
748}
749
750/// Draws a map in a single pass. Uses the silly team color thing that mindustry has.
751/// probably broken- if you want to use this ask me to update the block2color
752pub fn draw_map_simple(
753    map: &mut crate::data::map::MapReader,
754    r: Registrar,
755) -> Result<(Image<Box<[u8]>, 3>, (u16, u16)), super::map::ReadError> {
756    use std::ops::CoroutineState::*;
757    let mut co = map.thin_map(r)?;
758    let (w, h) = match Pin::new(&mut co).resume(()) {
759        Yielded(ThinMapData::Init { width, height }) => (width, height),
760        Complete(Err(x)) => return Err(x),
761        _ => unreachable!(),
762    };
763    let mut img = uninit::Image::<u8, 3>::new(
764        (w as u32).try_into().unwrap(),
765        (h as u32).try_into().unwrap(),
766    );
767    // loop1 draws the floor
768    for y in 0..h {
769        for x in 0..w {
770            let (floor, ore) = match Pin::new(&mut co).resume(()) {
771                Yielded(ThinMapData::Tile { floor, ore }) => (floor, ore),
772                Complete(Err(x)) => return Err(x),
773                _ => unreachable!(),
774            };
775            let t = (ore != Type::Air).then_some(ore).unwrap_or(floor);
776            let y = h - y - 1;
777            let i1 = img.at(x as u32, y as u32) as usize;
778            unsafe {
779                img.slice(i1..i1 + 3)
780                    .write_copy_of_slice(&BLOCK2COLOR[t as u16 as usize][..])
781            };
782        }
783    }
784    let mut img = unsafe { img.assume_init() }.boxed();
785    let mut i = 0;
786    while i < (w as usize * h as usize) {
787        let mut draw = |i, b: &'static crate::block::Block, team: Team| {
788            let x = i % w as usize;
789            let y = i / w as usize;
790            let y = h as usize - y - 1;
791            let s = b.get_size();
792            let x = x
793                - (match s {
794                    1 | 2 => 0,
795                    3 | 4 => 1,
796                    5 | 6 => 2,
797                    7 | 8 => 3,
798                    9 => 4,
799                    // SAFETY: no block too big
800                    _ => unsafe { std::hint::unreachable_unchecked() },
801                }) as usize;
802            let y = y
803                - (match s {
804                    1 => 0,
805                    2 | 3 => 1,
806                    4 | 5 => 2,
807                    6 | 7 => 3,
808                    8 | 9 => 4,
809                    // SAFETY: no block too big
810                    _ => unsafe { std::hint::unreachable_unchecked() },
811                }) as usize;
812            for x in x..(x as usize + s as usize).min(w as usize) {
813                for y in y..(y as usize + s as usize).min(h as usize) {
814                    unsafe {
815                        img.set_pixel(x as u32, y as u32, <[u8; 3]>::from(team.color()));
816                    }
817                }
818            }
819        };
820        match Pin::new(&mut co).resume(()) {
821            Yielded(ThinMapData::Bloc(ThinBloc::Many(None, n))) => {
822                i += n as usize;
823            }
824            Yielded(ThinMapData::Bloc(ThinBloc::Build(_, bloc, t))) => {
825                draw(i, bloc, t);
826            }
827            Yielded(ThinMapData::Bloc(ThinBloc::Many(Some(bloc), n))) => {
828                for i in i..=i + n as usize {
829                    draw(i, bloc, Team::DERELICT);
830                }
831                i += n as usize;
832            }
833            Complete(Err(x)) => return Err(x),
834            x => unreachable!("{x:?}"),
835        }
836        i += 1;
837    }
838    match Pin::new(&mut co).resume(()) {
839        Complete(Ok(())) => (),
840        f => unreachable!("{f:?}"),
841    };
842
843    Ok((img, (w, h)))
844}