veloren_world/site/plot/
tavern.rs

1use std::{mem::swap, ops::RangeInclusive};
2
3use common::{
4    comp::Content,
5    lottery::Lottery,
6    store::{Id, Store},
7    terrain::{BlockKind, SpriteCfg, SpriteKind},
8};
9use enum_map::EnumMap;
10use enumset::EnumSet;
11use hashbrown::HashSet;
12use rand::{
13    Rng,
14    seq::{IteratorRandom, SliceRandom},
15};
16use strum::{EnumIter, IntoEnumIterator};
17use vek::*;
18
19use crate::{
20    IndexRef, Land,
21    site::{Dir, Fill, Site, Structure, gen::PrimitiveTransform, namegen, util::Dir3},
22    util::RandomField,
23};
24
25type Neighbor = Option<Id<Room>>;
26
27pub struct Wall {
28    start: Vec2<i32>,
29    end: Vec2<i32>,
30    base_alt: i32,
31    top_alt: i32,
32    from: Neighbor,
33    to: Neighbor,
34    to_dir: Dir,
35    door: Option<(i32, i32)>,
36}
37
38impl Wall {
39    pub fn door_pos(&self) -> Option<Vec3<f32>> {
40        let wall_dir = Dir::from_vec2(self.end - self.start);
41
42        self.door.map(|(door_min, door_max)| {
43            (self.start.as_() + wall_dir.to_vec2().as_() * (door_min + door_max) as f32 / 2.0 + 0.5)
44                .with_z(self.base_alt as f32)
45        })
46    }
47
48    pub fn door_bounds(&self) -> Option<Aabr<i32>> {
49        let wall_dir = Dir::from_vec2(self.end - self.start);
50
51        self.door.map(|(door_min, door_max)| {
52            Aabr {
53                min: self.start + wall_dir.to_vec2() * door_min,
54                max: self.start + wall_dir.to_vec2() * door_max,
55            }
56            .made_valid()
57        })
58    }
59}
60
61#[derive(Copy, Clone)]
62enum RoofStyle {
63    Flat,
64    FlatBars { dir: Dir },
65    LeanTo { dir: Dir, max_z: i32 },
66    Gable { dir: Dir, max_z: i32 },
67    Hip { max_z: i32 },
68    Floor,
69}
70
71struct Roof {
72    bounds: Aabr<i32>,
73    min_z: i32,
74    style: RoofStyle,
75    stairs: Option<(Aabb<i32>, Dir)>,
76}
77
78#[derive(Clone, Copy, EnumIter, enum_map::Enum)]
79enum RoomKind {
80    Garden,
81    Stage,
82    Bar,
83    Seating,
84    Entrance,
85    Cellar,
86}
87
88impl RoomKind {
89    /// Returns the (side length size range, area size range)
90    fn size_range(&self) -> (RangeInclusive<i32>, RangeInclusive<i32>) {
91        match self {
92            RoomKind::Garden => (5..=20, 35..=250),
93            RoomKind::Seating => (4..=20, 35..=250),
94            RoomKind::Cellar => (6..=12, 35..=110),
95            RoomKind::Stage => (11..=22, 150..=400),
96            RoomKind::Bar => (9..=16, 80..=196),
97            RoomKind::Entrance => (3..=7, 12..=40),
98        }
99    }
100
101    fn chance(&self, room_counts: &EnumMap<RoomKind, u32>) -> f32 {
102        match self {
103            RoomKind::Garden => 0.05 / (1.0 + room_counts[RoomKind::Garden] as f32).powi(2),
104            RoomKind::Seating => 0.4 / (1.0 + room_counts[RoomKind::Seating] as f32),
105            RoomKind::Stage => match room_counts[RoomKind::Stage] {
106                0 => 1.0,
107                _ => 0.0,
108            },
109            RoomKind::Bar => match room_counts[RoomKind::Bar] {
110                0 => 1.0,
111                1 => 0.01,
112                _ => 0.0,
113            },
114            RoomKind::Entrance => 0.0,
115            RoomKind::Cellar => 1.0,
116        }
117    }
118
119    fn fits(&self, max_bounds: Aabr<i32>) -> bool {
120        // the smallest side on the maximum bounds
121        let max_min_size = max_bounds.size().reduce_min();
122        // max bounds area
123        let max_area = max_bounds.size().product();
124
125        let (size_range, area_range) = self.size_range();
126        *size_range.start() <= max_min_size && *area_range.start() <= max_area
127    }
128
129    fn entrance_room_lottery(temperature: f32) -> Lottery<RoomKind> {
130        let rooms = [
131            (0.5 * temperature, RoomKind::Garden),
132            (2.0, RoomKind::Entrance),
133        ]
134        .into_iter()
135        .filter(|(c, _)| *c > 0.0)
136        .collect::<Vec<_>>();
137
138        Lottery::from(rooms)
139    }
140
141    fn side_room_lottery(
142        &self,
143        max_bounds: Aabr<i32>,
144        room_counts: &EnumMap<RoomKind, u32>,
145        temperature: f32,
146    ) -> Option<Lottery<RoomKind>> {
147        let rooms: &[RoomKind] = match self {
148            RoomKind::Cellar => &[RoomKind::Cellar],
149            _ => &[
150                RoomKind::Stage,
151                RoomKind::Garden,
152                RoomKind::Bar,
153                RoomKind::Seating,
154            ],
155        };
156        let lottery = rooms.iter()
157                // Filter out rooms that won't fit here.
158                .filter(|kind| kind.fits(max_bounds))
159                // Calculate chance for each room.
160                .map(|room_kind| {
161                    let temp_scale = match room_kind {
162                        RoomKind::Garden => temperature,
163                        _ => 1.0,
164                    };
165
166                    (
167                        room_kind.chance(room_counts) * temp_scale,
168                        *room_kind,
169                    )
170                })
171                .filter(|(c, _)| *c > 0.0)
172                .collect::<Vec<_>>();
173
174        if lottery.is_empty() {
175            return None;
176        }
177
178        Some(Lottery::from(lottery))
179    }
180
181    fn basement_rooms(&self) -> &'static [RoomKind] {
182        match self {
183            RoomKind::Bar => &[RoomKind::Cellar],
184            _ => &[],
185        }
186    }
187
188    fn basement_lottery(
189        &self,
190        max_bounds: Aabr<i32>,
191        room_counts: &EnumMap<RoomKind, u32>,
192    ) -> Option<Lottery<RoomKind>> {
193        let lottery = self.basement_rooms().iter()
194                // Filter out rooms that won't fit here.
195                .filter(|kind| kind.fits(max_bounds))
196                // Calculate chance for each room.
197                .map(|room_kind| {
198                    (
199                        room_kind.chance(room_counts),
200                        *room_kind,
201                    )
202                })
203                .collect::<Vec<_>>();
204
205        if lottery.is_empty() {
206            return None;
207        }
208
209        Some(Lottery::from(lottery))
210    }
211}
212
213#[derive(Clone, Copy)]
214pub enum Detail {
215    Bar {
216        aabr: Aabr<i32>,
217    },
218    Table {
219        pos: Vec2<i32>,
220        chairs: EnumSet<Dir>,
221    },
222    Stage {
223        aabr: Aabr<i32>,
224    },
225}
226
227pub struct Room {
228    /// Inclusive
229    pub bounds: Aabb<i32>,
230    kind: RoomKind,
231    // stairs: Option<Id<Stairs>>,
232    walls: EnumMap<Dir, Vec<Id<Wall>>>,
233    floors: Vec<Id<Roof>>,
234    roofs: Vec<Id<Roof>>,
235    detail_areas: Vec<Aabr<i32>>,
236    pub details: Vec<Detail>,
237}
238
239impl Room {
240    fn new(bounds: Aabb<i32>, kind: RoomKind) -> Self {
241        Self {
242            bounds,
243            kind,
244            floors: Default::default(),
245            roofs: Default::default(),
246            walls: Default::default(),
247            detail_areas: Default::default(),
248            details: Default::default(),
249        }
250    }
251
252    /// Are any of this rooms roofs fully covering it?
253    fn is_covered_by_roof(&self, roofs: &Store<Roof>) -> bool {
254        let aabr = Aabr {
255            min: self.bounds.min.xy(),
256            max: self.bounds.max.xy(),
257        };
258        for roof in self.roofs.iter() {
259            if roofs[*roof].bounds.contains_aabr(aabr) {
260                return true;
261            }
262        }
263        false
264    }
265}
266
267pub struct Tavern {
268    pub name: String,
269    pub rooms: Store<Room>,
270    walls: Store<Wall>,
271    roofs: Store<Roof>,
272    /// Tile position of the door tile
273    pub door_tile: Vec2<i32>,
274    pub door_wpos: Vec3<i32>,
275    /// Axis aligned bounding region for the house
276    pub bounds: Aabr<i32>,
277}
278
279impl Tavern {
280    pub fn generate(
281        land: &Land,
282        _index: IndexRef,
283        rng: &mut impl Rng,
284        site: &Site,
285        door_tile: Vec2<i32>,
286        door_dir: Dir,
287        tile_aabr: Aabr<i32>,
288        alt: Option<i32>,
289    ) -> Self {
290        let name = namegen::NameGen::location(rng).generate_tavern();
291
292        let mut rooms = Store::default();
293        let mut walls = Store::default();
294        let mut roofs = Store::default();
295        let mut room_counts = EnumMap::<RoomKind, u32>::default();
296
297        let bounds = Aabr {
298            min: site.tile_wpos(tile_aabr.min),
299            max: site.tile_wpos(tile_aabr.max),
300        };
301
302        let ibounds = Aabr {
303            min: bounds.min + 1,
304            max: bounds.max - 2,
305        };
306
307        let door_tile_center = site.tile_center_wpos(door_tile);
308        let door_wpos = door_dir.select_aabr_with(ibounds, door_tile_center);
309        let temperature = land.get_interpolated(door_wpos, |c| c.temp);
310
311        let door_alt = alt.unwrap_or_else(|| land.get_alt_approx(door_wpos).ceil() as i32);
312        let door_wpos = door_wpos.with_z(door_alt);
313
314        fn gen_range_snap(rng: &mut impl Rng, range: RangeInclusive<i32>, snap_max: i32) -> i32 {
315            let res = rng.gen_range(range.clone());
316            if snap_max <= *range.end() && snap_max - res <= 2 {
317                snap_max
318            } else {
319                res
320            }
321        }
322
323        /// Place room in bounds.
324        fn place_side_room(
325            room: RoomKind,
326            max_bounds: Aabr<i32>,
327            in_dir: Dir,
328            in_pos: Vec2<i32>,
329            rng: &mut impl Rng,
330        ) -> Option<Aabr<i32>> {
331            let (size_range, area_range) = room.size_range();
332            let min = *size_range.start();
333            let snap_max = in_dir.select(max_bounds.size());
334            let max = snap_max.min(*size_range.end());
335            if max < min {
336                return None;
337            }
338            let size_x = gen_range_snap(rng, min..=max, snap_max);
339
340            let min = ((*area_range.start() + size_x - 1) / size_x).max(*size_range.start());
341            let snap_max = in_dir.orthogonal().select(max_bounds.size());
342            let max = snap_max
343                .min(*size_range.end())
344                .min(*area_range.end() / size_x);
345
346            if max < min {
347                return None;
348            }
349            let size_y = gen_range_snap(rng, min..=max, snap_max);
350
351            // calculate a valid aabr
352            let half_size_y = size_y / 2 + (size_y % 2) * rng.gen_range(0..=1);
353            let min = in_pos + in_dir.to_vec2() + in_dir.rotated_cw().to_vec2() * half_size_y;
354            let min = max_bounds.projected_point(min);
355            let max = min + in_dir.to_vec2() * size_x + in_dir.rotated_ccw().to_vec2() * size_y;
356            let max = max_bounds.projected_point(max);
357            let min = max - in_dir.to_vec2() * size_x + in_dir.rotated_cw().to_vec2() * size_y;
358
359            let bounds = Aabr { min, max }.made_valid();
360            Some(bounds)
361        }
362
363        fn place_down_room(
364            room: RoomKind,
365            max_bounds: Aabr<i32>,
366            from_bounds: Aabr<i32>,
367            rng: &mut impl Rng,
368        ) -> Option<Aabr<i32>> {
369            let (size_range, area_range) = room.size_range();
370            let min = Vec2::broadcast(*size_range.start());
371            let max = Vec2::from(max_bounds.size()).map(|e: i32| e.min(*size_range.end()));
372
373            let size_x = gen_range_snap(rng, min.x..=max.x, max_bounds.size().w);
374            let size_y = gen_range_snap(
375                rng,
376                min.y.max(area_range.start() / size_x)..=max.y.min(area_range.end() / size_x),
377                max_bounds.size().h,
378            );
379            let target_size = Vec2::new(size_x, size_y);
380            let dir = Dir::choose(rng);
381            let orth = *[dir.orthogonal(), dir.orthogonal().opposite()]
382                .choose(rng)
383                .unwrap();
384
385            let plane = dir.to_vec2() + orth.to_vec2();
386            let corner = dir.select_aabr_with(from_bounds, orth.select_aabr(from_bounds));
387            let aabr = Aabr {
388                min: corner,
389                max: corner - plane * target_size,
390            }
391            .made_valid();
392
393            let inside = aabr.intersection(max_bounds);
394            let mv = target_size - inside.size();
395            let aabr = Aabr {
396                min: aabr.min + mv * plane,
397                max: aabr.max + mv * plane,
398            };
399
400            let aabr = aabr.intersection(max_bounds);
401
402            let area = aabr.size().product();
403            if aabr.is_valid()
404                && area_range.contains(&area)
405                && size_range.contains(&aabr.size().reduce_min())
406            {
407                Some(aabr)
408            } else {
409                None
410            }
411        }
412
413        struct RoomMeta {
414            id: Id<Room>,
415            free_walls: EnumSet<Dir>,
416            can_add_basement: bool,
417        }
418
419        let mut room_metas = Vec::new();
420
421        {
422            let entrance_rooms = RoomKind::entrance_room_lottery(temperature);
423
424            let entrance_room = *entrance_rooms.choose_seeded(rng.gen());
425            let entrance_room_hgt = rng.gen_range(3..=4);
426            let entrance_room_aabr =
427                place_side_room(entrance_room, ibounds, -door_dir, door_wpos.xy(), rng)
428                    .expect("Not enough room in plot for a tavern");
429            let entrance_room_aabb = Aabb {
430                min: entrance_room_aabr.min.with_z(door_wpos.z),
431                max: entrance_room_aabr
432                    .max
433                    .with_z(door_wpos.z + entrance_room_hgt),
434            }
435            .made_valid();
436
437            let entrance_id = rooms.insert(Room::new(entrance_room_aabb, entrance_room));
438
439            let start = door_dir.select_aabr_with(
440                entrance_room_aabr,
441                Vec2::broadcast(door_dir.rotated_cw().select_aabr(entrance_room_aabr)),
442            ) + door_dir.rotated_cw().to_vec2()
443                + door_dir.to_vec2();
444            let door_center = door_dir.rotated_cw().select(door_wpos.xy() - start).abs();
445            let wall_id = walls.insert(Wall {
446                start,
447                end: door_dir.select_aabr_with(
448                    entrance_room_aabr,
449                    Vec2::broadcast(door_dir.rotated_ccw().select_aabr(entrance_room_aabr)),
450                ) + door_dir.rotated_ccw().to_vec2()
451                    + door_dir.to_vec2(),
452                base_alt: entrance_room_aabb.min.z,
453                top_alt: entrance_room_aabb.max.z,
454                from: None,
455                to: Some(entrance_id),
456                to_dir: -door_dir,
457                door: Some((door_center - 1, door_center + 1)),
458            });
459            rooms[entrance_id].walls[door_dir].push(wall_id);
460
461            room_metas.push(RoomMeta {
462                id: entrance_id,
463                free_walls: Dir::iter().filter(|d| *d != door_dir).collect(),
464                can_add_basement: false,
465            });
466
467            room_counts[entrance_room] += 1;
468        }
469
470        fn to_aabr(aabb: Aabb<i32>) -> Aabr<i32> {
471            Aabr {
472                min: aabb.min.xy(),
473                max: aabb.max.xy(),
474            }
475        }
476
477        fn extend_aabr(aabr: Aabr<i32>, amount: i32) -> Aabr<i32> {
478            Aabr {
479                min: aabr.min - amount,
480                max: aabr.max + amount,
481            }
482        }
483        while !room_metas.is_empty() {
484            // Continue extending from a random existing room
485            let mut room_meta = room_metas.swap_remove(rng.gen_range(0..room_metas.len()));
486            let from_id = room_meta.id;
487            let from_room = &rooms[from_id];
488
489            let from_bounds = to_aabr(from_room.bounds);
490
491            fn fit_room<'r>(
492                rooms: impl Iterator<Item = &'r Room>,
493                min_z: i32,
494                max_z: i32,
495                mut max_bounds: Aabr<i32>,
496                mut max_shrink_dir: impl FnMut(Dir) -> Option<i32>,
497            ) -> Option<Aabr<i32>> {
498                // Take other rooms into account when calculating `max_bounds`. We don't care
499                // about this room if it's the originating room or at another
500                // height.
501                for room in rooms
502                    .filter(|room| room.bounds.min.z - 1 <= max_z && room.bounds.max.z + 1 >= min_z)
503                {
504                    let test_bounds = to_aabr(room.bounds);
505                    let bounds = extend_aabr(test_bounds, 2);
506                    let intersection = bounds.intersection(max_bounds);
507                    if intersection.is_valid() {
508                        // Find the direction to shrink in that yields the highest area.
509                        let bounds = Dir::iter()
510                            .filter(|dir| {
511                                dir.select_aabr(intersection) * dir.signum()
512                                    < dir.select_aabr(max_bounds) * dir.signum()
513                            })
514                            .map(|min_dir| {
515                                let max_shrink = max_shrink_dir(min_dir);
516                                let limit = min_dir.vec2_abs(max_shrink, None);
517                                Aabr {
518                                    min: min_dir.select_aabr_with(
519                                        max_bounds,
520                                        Vec2::broadcast(
521                                            min_dir.rotated_ccw().select_aabr(max_bounds),
522                                        ),
523                                    ),
524                                    max: min_dir
525                                        .select_aabr_with(
526                                            intersection,
527                                            Vec2::broadcast(
528                                                min_dir.rotated_cw().select_aabr(max_bounds),
529                                            ),
530                                        )
531                                        .map2(limit, |a, b| {
532                                            b.map_or(a, |b| {
533                                                (a * min_dir.signum()).min(b * min_dir.signum())
534                                                    * min_dir.signum()
535                                            })
536                                        }),
537                                }
538                                .made_valid()
539                            })
540                            .filter(|bounds| !bounds.intersection(test_bounds).is_valid())
541                            .max_by_key(|bounds| bounds.size().product())?;
542
543                        max_bounds = bounds;
544                    }
545                }
546                Some(max_bounds)
547            }
548            'room_gen: {
549                if let Some(in_dir) = room_meta.free_walls.into_iter().choose(rng) {
550                    room_meta.free_walls.remove(in_dir);
551                    let right = in_dir.orthogonal();
552                    let left = -right;
553
554                    // The maximum bounds, limited by the plot bounds and other rooms.
555                    let max_bounds = Aabr {
556                        min: in_dir.select_aabr_with(from_bounds, ibounds.min)
557                            + in_dir.to_vec2() * 2,
558                        max: in_dir.select_aabr_with(ibounds, ibounds.max),
559                    }
560                    .made_valid();
561                    // Pick a  height of the new room
562                    let room_hgt = rng.gen_range(3..=5);
563
564                    let wanted_alt = land.get_alt_approx(max_bounds.center()) as i32 + 1;
565                    let max_stair_length =
566                        (in_dir.select(if wanted_alt < from_room.bounds.min.z {
567                            from_bounds.size()
568                        } else {
569                            max_bounds.size()
570                        }) / 2)
571                            .min(5);
572                    let alt = wanted_alt.clamp(
573                        from_room.bounds.min.z - max_stair_length,
574                        from_room.bounds.min.z + max_stair_length,
575                    );
576                    let min_z = from_room.bounds.min.z.min(alt);
577                    let max_z = from_room.bounds.max.z.max(alt + room_hgt);
578
579                    let Some(max_bounds) = fit_room(
580                        rooms
581                            .iter()
582                            .filter(|(id, _)| *id != from_id)
583                            .map(|(_, r)| r),
584                        min_z,
585                        max_z,
586                        max_bounds,
587                        |dir| {
588                            if dir == in_dir {
589                                // We always want the wall to stay in exact contact on the side the
590                                // door is.
591                                Some(dir.opposite().select_aabr(max_bounds))
592                            } else if dir == in_dir.opposite() {
593                                None
594                            } else {
595                                // We want the wall to touch at some point along the orthogonal axis
596                                // to the door.
597                                Some(dir.select_aabr(extend_aabr(from_bounds, -1)))
598                            }
599                        },
600                    ) else {
601                        break 'room_gen;
602                    };
603
604                    let Some(room_lottery) = rooms[room_meta.id].kind.side_room_lottery(
605                        max_bounds,
606                        &room_counts,
607                        temperature,
608                    ) else {
609                        // We have no rooms to pick from
610                        break 'room_gen;
611                    };
612
613                    let room_kind = *room_lottery.choose_seeded(rng.gen());
614
615                    // Select a door position
616                    let mut min = left
617                        .select_aabr(from_bounds)
618                        .max(left.select_aabr(max_bounds));
619                    let mut max = right
620                        .select_aabr(from_bounds)
621                        .min(right.select_aabr(max_bounds));
622                    if max < min {
623                        swap(&mut min, &mut max);
624                    }
625                    if min + 2 > max {
626                        break 'room_gen;
627                    }
628                    let in_pos = rng.gen_range(min + 1..=max - 1);
629                    let in_pos = in_dir.select_aabr_with(from_bounds, Vec2::broadcast(in_pos))
630                        + in_dir.to_vec2();
631
632                    // Place the room in the given max bounds
633                    let Some(bounds) = place_side_room(room_kind, max_bounds, in_dir, in_pos, rng)
634                    else {
635                        break 'room_gen;
636                    };
637
638                    let bounds3 = Aabb {
639                        min: bounds.min.with_z(min_z),
640                        max: bounds.max.with_z(max_z),
641                    };
642                    let id = rooms.insert(Room::new(bounds3, room_kind));
643
644                    let start = in_dir.select_aabr_with(
645                        from_bounds,
646                        Vec2::broadcast(
647                            left.select_aabr(from_bounds).max(left.select_aabr(bounds)),
648                        ),
649                    ) + in_dir.to_vec2()
650                        + left.to_vec2();
651
652                    let end = in_dir.select_aabr_with(
653                        from_bounds,
654                        Vec2::broadcast(
655                            right
656                                .select_aabr(from_bounds)
657                                .min(right.select_aabr(bounds)),
658                        ),
659                    ) + in_dir.to_vec2()
660                        + right.to_vec2();
661
662                    let door_center = right.select(in_pos - start);
663                    let b = rng.gen_bool(0.5);
664                    let door_min = door_center - b as i32;
665                    let door_max = door_center - (!b) as i32;
666                    let wall_id = walls.insert(Wall {
667                        start,
668                        end,
669                        base_alt: min_z,
670                        top_alt: max_z,
671                        from: Some(from_id),
672                        to: Some(id),
673                        to_dir: in_dir,
674                        door: Some((door_min, door_max)),
675                    });
676
677                    rooms[id].walls[-in_dir].push(wall_id);
678                    rooms[from_id].walls[in_dir].push(wall_id);
679
680                    room_metas.push(RoomMeta {
681                        id,
682                        free_walls: Dir::iter().filter(|d| *d != -in_dir).collect(),
683                        can_add_basement: !room_kind.basement_rooms().is_empty(),
684                    });
685                    room_counts[room_kind] += 1;
686                } else if room_meta.can_add_basement {
687                    room_meta.can_add_basement = false;
688                    let max_bounds = ibounds;
689
690                    // Pick a  height of the new room
691                    let room_hgt = rng.gen_range(3..=5);
692                    let max_z = from_room.bounds.min.z - 2;
693                    let min_z = max_z - room_hgt;
694
695                    let Some(max_bounds) = fit_room(
696                        rooms
697                            .iter()
698                            .filter(|(id, _)| *id != from_id)
699                            .map(|(_, r)| r),
700                        min_z,
701                        max_z,
702                        max_bounds,
703                        |dir| Some(dir.opposite().select_aabr(extend_aabr(from_bounds, -2))),
704                    ) else {
705                        break 'room_gen;
706                    };
707
708                    let Some(room_lottery) = rooms[room_meta.id]
709                        .kind
710                        .basement_lottery(max_bounds, &room_counts)
711                    else {
712                        // We have no rooms to pick from
713                        break 'room_gen;
714                    };
715
716                    let room_kind = *room_lottery.choose_seeded(rng.gen());
717
718                    // Place the room in the given max bounds
719                    let Some(bounds) = place_down_room(room_kind, max_bounds, from_bounds, rng)
720                    else {
721                        break 'room_gen;
722                    };
723
724                    let bounds3 = Aabb {
725                        min: bounds.min.with_z(min_z),
726                        max: bounds.max.with_z(max_z),
727                    };
728                    let id = rooms.insert(Room::new(bounds3, room_kind));
729
730                    room_metas.push(RoomMeta {
731                        id,
732                        free_walls: EnumSet::all(),
733                        can_add_basement: !room_kind.basement_rooms().is_empty(),
734                    });
735                    room_counts[room_kind] += 1;
736                } else {
737                    break 'room_gen;
738                };
739            }
740
741            // If there are more directions to continue from, push this room again.
742            if !room_meta.free_walls.is_empty() || room_meta.can_add_basement {
743                room_metas.push(room_meta);
744            }
745        }
746
747        // Place walls where needed.
748        for from_id in rooms.ids() {
749            let room_bounds = to_aabr(rooms[from_id].bounds);
750            let mut skip = HashSet::new();
751            skip.insert(from_id);
752            let mut wall_ranges = EnumMap::<Dir, Vec<_>>::default();
753            for dir in Dir::iter() {
754                let orth = dir.orthogonal();
755                let range = (orth.select(room_bounds.min), orth.select(room_bounds.max));
756                wall_ranges[dir].push(range);
757            }
758            // Split the wall into parts.
759            let mut split_range = |dir: Dir, min: i32, max: i32| {
760                debug_assert!(min <= max);
761                let mut new_ranges = Vec::new();
762                wall_ranges[dir].retain_mut(|(r_min, r_max)| {
763                    if *r_min <= max && *r_max >= min {
764                        match (*r_min >= min, *r_max <= max) {
765                            (true, true) => false,
766                            (true, false) => {
767                                *r_min = max + 1;
768                                true
769                            },
770                            (false, true) => {
771                                *r_max = min - 1;
772                                true
773                            },
774                            (false, false) => {
775                                new_ranges.push((max + 1, *r_max));
776                                *r_max = min - 1;
777                                true
778                            },
779                        }
780                    } else {
781                        true
782                    }
783                });
784                wall_ranges[dir].extend(new_ranges);
785            };
786            for dir in Dir::iter() {
787                let connected_walls = &mut rooms[from_id].walls[dir];
788                skip.extend(
789                    connected_walls
790                        .iter()
791                        .flat_map(|wall| walls[*wall].from.into_iter().chain(walls[*wall].to)),
792                );
793                let orth = dir.orthogonal();
794                // Divide wall ranges by existing walls.
795                for wall in connected_walls.iter() {
796                    let wall = &walls[*wall];
797                    let mut min = orth.select(wall.start);
798                    let mut max = orth.select(wall.end);
799                    if min > max {
800                        swap(&mut min, &mut max);
801                    }
802                    min += 1;
803                    max -= 1;
804                    split_range(dir, min, max);
805                }
806            }
807
808            // Divide wall ranges by neighbouring rooms
809            for to_id in rooms.ids().filter(|id| !skip.contains(id)) {
810                let a_min_z = rooms[from_id].bounds.min.z;
811                let a_max_z = rooms[from_id].bounds.max.z;
812                let b_min_z = rooms[to_id].bounds.min.z;
813                let b_max_z = rooms[to_id].bounds.max.z;
814                if a_min_z >= b_max_z || a_max_z <= b_min_z {
815                    // We are not at the same altitude.
816                    continue;
817                }
818                let min_z = a_min_z.min(b_min_z);
819                let max_z = a_max_z.max(b_max_z);
820                let n_room_bounds = to_aabr(rooms[to_id].bounds);
821
822                let p1 = n_room_bounds.projected_point(room_bounds.center());
823                let p0 = room_bounds.projected_point(p1);
824
825                let to_dir = Dir::from_vec2(p1 - p0);
826
827                let intersection = to_dir
828                    .extend_aabr(room_bounds, 1)
829                    .intersection(to_dir.opposite().extend_aabr(n_room_bounds, 1));
830
831                if intersection.is_valid() {
832                    let start = intersection.min;
833                    let end = intersection.max;
834
835                    let orth = to_dir.orthogonal();
836
837                    let min = orth.select(start);
838                    let max = orth.select(end);
839                    split_range(to_dir, min, max);
840                    let door = if max - min > 2
841                        && max_z - min_z > 3
842                        && (rooms[from_id].bounds.min.z - rooms[to_id].bounds.min.z).abs() < 4
843                        && rng.gen_bool(0.8)
844                    {
845                        let door_center = rng.gen_range(1..=max - min - 2);
846                        Some((door_center, door_center + 1))
847                    } else {
848                        None
849                    };
850
851                    let id = walls.insert(Wall {
852                        start: start - orth.to_vec2(),
853                        end: end + orth.to_vec2(),
854                        base_alt: min_z,
855                        top_alt: max_z,
856                        from: Some(from_id),
857                        to: Some(to_id),
858                        to_dir,
859                        door,
860                    });
861
862                    rooms[from_id].walls[to_dir].push(id);
863                    rooms[to_id].walls[-to_dir].push(id);
864                }
865            }
866            // Place remaining walls.
867            for (dir, ranges) in wall_ranges {
868                for (min, max) in ranges {
869                    let start =
870                        dir.select_aabr_with(room_bounds, Vec2::broadcast(min - 1)) + dir.to_vec2();
871                    let end =
872                        dir.select_aabr_with(room_bounds, Vec2::broadcast(max + 1)) + dir.to_vec2();
873
874                    let wall_id = walls.insert(Wall {
875                        start,
876                        end,
877                        base_alt: rooms[from_id].bounds.min.z,
878                        top_alt: rooms[from_id].bounds.max.z,
879                        from: Some(from_id),
880                        to: None,
881                        to_dir: dir,
882                        door: None,
883                    });
884
885                    rooms[from_id].walls[dir].push(wall_id);
886                }
887            }
888        }
889
890        for room_id in rooms.ids() {
891            let room = &rooms[room_id];
892            // If a room is already fully covered by a roof, we skip it.
893            if room.is_covered_by_roof(&roofs) {
894                continue;
895            }
896            let roof_min_z = room.bounds.max.z + 1;
897            let mut roof_bounds = to_aabr(room.bounds);
898            roof_bounds.min -= 2;
899            roof_bounds.max += 2;
900            let mut dirs = Vec::from(Dir::ALL);
901
902            let mut over_rooms = vec![room_id];
903            let mut under_rooms = vec![];
904            // Extend roof over adjecent rooms.
905            while !dirs.is_empty() {
906                let dir = dirs.swap_remove(rng.gen_range(0..dirs.len()));
907                let orth = dir.orthogonal();
908                // Check for room intersections in this direction.
909                for (room_id, room) in rooms.iter() {
910                    let room_aabr = to_aabr(room.bounds);
911                    if room.bounds.max.z + 1 == roof_min_z
912                        && dir.select_aabr(roof_bounds) + dir.signum()
913                            == (-dir).select_aabr(room_aabr)
914                        && orth.select_aabr(roof_bounds) <= orth.select_aabr(room_aabr) + 2
915                        && (-orth).select_aabr(roof_bounds) >= (-orth).select_aabr(room_aabr) - 2
916                    {
917                        // If the room we found is fully covered by a roof already, we don't go in
918                        // this direction.
919                        if room.is_covered_by_roof(&roofs) {
920                            break;
921                        }
922                        roof_bounds = dir.extend_aabr(roof_bounds, dir.select(room_aabr.size()));
923                        dirs.push(dir);
924                        over_rooms.push(room_id);
925                        break;
926                    }
927                }
928            }
929            for (room_id, room) in rooms.iter() {
930                let room_aabr = to_aabr(room.bounds);
931                if room.bounds.min.z - 1 == roof_min_z && room_aabr.collides_with_aabr(roof_bounds)
932                {
933                    under_rooms.push(room_id);
934                }
935            }
936
937            let valid_styles = if !under_rooms.is_empty() {
938                vec![(1.0, RoofStyle::Floor)]
939            } else {
940                // Build a lottery of valid roofs to pick from
941                let mut valid_styles = vec![(0.5, RoofStyle::Flat)];
942
943                let gardens = over_rooms
944                    .iter()
945                    .filter(|id| matches!(rooms[**id].kind, RoomKind::Garden))
946                    .count();
947
948                // If we just have gardens, we can use FlatBars style.
949                if gardens == over_rooms.len() {
950                    let ratio = Dir::X.select(roof_bounds.size()) as f32
951                        / Dir::Y.select(roof_bounds.size()) as f32;
952                    valid_styles.extend([
953                        (5.0 * ratio, RoofStyle::FlatBars { dir: Dir::X }),
954                        (5.0 / ratio, RoofStyle::FlatBars { dir: Dir::Y }),
955                    ]);
956                }
957
958                // Find heights of possible adjecent rooms.
959                let mut dir_zs = EnumMap::default();
960                for dir in Dir::iter() {
961                    let orth = dir.orthogonal();
962                    for room in rooms.values() {
963                        let room_aabr = to_aabr(room.bounds);
964                        if room.bounds.max.z > roof_min_z
965                            && dir.select_aabr(roof_bounds) == (-dir).select_aabr(room_aabr)
966                            && orth.select_aabr(roof_bounds) <= orth.select_aabr(room_aabr) + 2
967                            && (-orth).select_aabr(roof_bounds)
968                                >= (-orth).select_aabr(room_aabr) - 2
969                        {
970                            dir_zs[dir] = Some(room.bounds.max.z);
971                            break;
972                        }
973                    }
974                }
975
976                for dir in [Dir::X, Dir::Y] {
977                    if dir_zs[dir.orthogonal()].is_none() && dir_zs[-dir.orthogonal()].is_none() {
978                        let max_z = roof_min_z
979                            + (dir.orthogonal().select(roof_bounds.size()) / 2 - 1).min(7);
980                        let max_z = match (dir_zs[dir], dir_zs[-dir]) {
981                            (Some(a), Some(b)) => {
982                                if a.min(b) >= roof_min_z + 3 {
983                                    max_z.min(a.min(b))
984                                } else {
985                                    max_z
986                                }
987                            },
988                            (None, None) => max_z,
989                            _ => continue,
990                        };
991
992                        for max_z in roof_min_z + 3..=max_z {
993                            valid_styles.push((1.0, RoofStyle::Gable { dir, max_z }))
994                        }
995                    }
996                }
997
998                for dir in Dir::iter() {
999                    if let (Some(h), None) = (dir_zs[dir], dir_zs[-dir]) {
1000                        for max_z in roof_min_z + 2..=h {
1001                            valid_styles.push((1.0, RoofStyle::LeanTo { dir, max_z }))
1002                        }
1003                    }
1004                }
1005
1006                if Dir::iter().all(|d| dir_zs[d].is_none()) {
1007                    for max_z in roof_min_z + 3..=roof_min_z + 7 {
1008                        valid_styles.push((0.8, RoofStyle::Hip { max_z }))
1009                    }
1010                }
1011
1012                valid_styles
1013            };
1014
1015            let style_lottery = Lottery::from(valid_styles);
1016
1017            debug_assert!(
1018                roof_bounds.is_valid(),
1019                "Roof bounds aren't valid: {:?}",
1020                roof_bounds
1021            );
1022
1023            let stairs = under_rooms
1024                .iter()
1025                .copied()
1026                .flat_map(|to_room| {
1027                    let rooms = &rooms;
1028                    let walls = &walls;
1029                    over_rooms
1030                        .iter()
1031                        .copied()
1032                        .filter_map(move |in_room| {
1033                            let to_room_bounds = rooms[to_room].bounds;
1034                            let in_room_bounds = rooms[in_room].bounds;
1035                            let max_bounds =
1036                                to_aabr(to_room_bounds).intersection(to_aabr(in_room_bounds));
1037                            let stair_length = to_room_bounds.min.z - 1 - in_room_bounds.min.z;
1038                            if !max_bounds.is_valid()
1039                                || max_bounds.size().reduce_min() <= stair_length
1040                            {
1041                                return None;
1042                            }
1043
1044                            let in_aabr = to_aabr(in_room_bounds);
1045                            let to_aabr = to_aabr(to_room_bounds);
1046
1047                            let valid_dirs = Dir::iter().filter(move |dir| {
1048                                dir.select_aabr(in_aabr) == dir.select_aabr(max_bounds)
1049                                    || dir.select_aabr(to_aabr) == dir.select_aabr(max_bounds)
1050                            });
1051
1052                            Some(valid_dirs.clone().flat_map(move |dir| {
1053                                valid_dirs
1054                                    .clone()
1055                                    .filter(move |d| d.abs() != dir.abs())
1056                                    .filter_map(move |orth| {
1057                                        let stair_width = 2;
1058                                        let stair_aabr = orth.trim_aabr(
1059                                            dir.trim_aabr(
1060                                                max_bounds,
1061                                                dir.select(max_bounds.size()) - stair_length,
1062                                            ),
1063                                            orth.select(max_bounds.size()) - stair_width + 1,
1064                                        );
1065
1066                                        let test_aabr = Aabr {
1067                                            min: stair_aabr.min - 1,
1068                                            max: stair_aabr.max - 1,
1069                                        };
1070                                        if !stair_aabr.is_valid()
1071                                            || rooms[in_room]
1072                                                .walls
1073                                                .values()
1074                                                .chain(rooms[to_room].walls.values())
1075                                                .flatten()
1076                                                .any(|wall| {
1077                                                    walls[*wall].door_bounds().is_some_and(
1078                                                        |door_bounds| {
1079                                                            test_aabr
1080                                                                .collides_with_aabr(door_bounds)
1081                                                        },
1082                                                    )
1083                                                })
1084                                        {
1085                                            return None;
1086                                        }
1087
1088                                        Some((
1089                                            Aabb {
1090                                                min: stair_aabr.min.with_z(in_room_bounds.min.z),
1091                                                max: stair_aabr
1092                                                    .max
1093                                                    .with_z(to_room_bounds.min.z - 1),
1094                                            },
1095                                            dir,
1096                                        ))
1097                                    })
1098                            }))
1099                        })
1100                        .flatten()
1101                })
1102                .choose(rng);
1103
1104            let roof_id = roofs.insert(Roof {
1105                bounds: roof_bounds,
1106                min_z: roof_min_z,
1107                stairs,
1108                style: *style_lottery.choose_seeded(rng.gen()),
1109            });
1110
1111            for room_id in over_rooms {
1112                rooms[room_id].roofs.push(roof_id);
1113            }
1114            for room_id in under_rooms {
1115                rooms[room_id].floors.push(roof_id);
1116            }
1117        }
1118
1119        // Compute detail areas
1120        for room in rooms.values_mut() {
1121            let bounds = to_aabr(room.bounds);
1122            let walls = &walls;
1123            let mut avoid = room
1124                .walls
1125                .iter()
1126                .flat_map(|(dir, dir_walls)| {
1127                    dir_walls.iter().filter_map(move |wall_id| {
1128                        let wall = &walls[*wall_id];
1129
1130                        let door_bounds = wall.door_bounds()?;
1131
1132                        Some(
1133                            Aabr {
1134                                min: dir.select_aabr_with(bounds, door_bounds.min),
1135                                max: dir.select_with(bounds.center(), door_bounds.max),
1136                            }
1137                            .made_valid(),
1138                        )
1139                    })
1140                })
1141                .chain(
1142                    room.floors
1143                        .iter()
1144                        .chain(room.roofs.iter())
1145                        .filter_map(|roof| {
1146                            let aabr = to_aabr(roofs[*roof].stairs?.0);
1147                            let intersection = aabr.intersection(bounds);
1148                            intersection.is_valid().then_some(intersection)
1149                        }),
1150                )
1151                .collect::<Vec<_>>();
1152
1153            let mut x = bounds.min.x;
1154            // Basically greedy meshing, but for aabrs
1155            while x <= bounds.max.x {
1156                let mut y = bounds.min.y;
1157                'y_loop: while y <= bounds.max.y {
1158                    let min = Vec2::new(x, y);
1159                    let mut max_y = bounds.max.y;
1160                    for area in avoid.iter() {
1161                        let contains_x = area.min.x <= min.x && min.x <= area.max.x;
1162                        let contains_y = area.min.y <= min.y && min.y <= area.max.y;
1163                        if contains_x && contains_y {
1164                            y = area.max.y + 1;
1165                            continue 'y_loop;
1166                        }
1167
1168                        if contains_x && min.y < area.min.y && area.min.y - 1 < max_y {
1169                            max_y = area.min.y - 1;
1170                        }
1171                    }
1172
1173                    let max_x = avoid
1174                        .iter()
1175                        .filter_map(|area| {
1176                            if area.min.x > x && area.min.y <= max_y && area.max.y >= min.y {
1177                                Some(area.min.x - 1)
1178                            } else {
1179                                None
1180                            }
1181                        })
1182                        .min()
1183                        .unwrap_or(bounds.max.x);
1184
1185                    let area = Aabr {
1186                        min,
1187                        max: Vec2::new(max_x, max_y),
1188                    };
1189                    avoid.push(area);
1190                    room.detail_areas.push(area);
1191                    y = max_y + 1;
1192                }
1193                x += 1;
1194            }
1195        }
1196
1197        // Place details in detail areas.
1198        for room in rooms.values_mut() {
1199            let room_aabr = to_aabr(room.bounds);
1200            let table = |pos: Vec2<i32>, aabr: Aabr<i32>| Detail::Table {
1201                pos,
1202                chairs: Dir::iter()
1203                    .filter(|dir| aabr.contains_point(pos + dir.to_vec2()))
1204                    .collect(),
1205            };
1206            match room.kind {
1207                RoomKind::Garden | RoomKind::Seating => room.detail_areas.retain(|&aabr| {
1208                    if aabr.size().reduce_max() > 1 && rng.gen_bool(0.7) {
1209                        room.details.push(table(aabr.center(), aabr));
1210                        false
1211                    } else {
1212                        true
1213                    }
1214                }),
1215                RoomKind::Cellar => {},
1216                RoomKind::Stage => {
1217                    let mut best = None;
1218                    let mut best_score = 0;
1219                    for (i, aabr) in room.detail_areas.iter().enumerate() {
1220                        let edges = Dir::iter()
1221                            .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1222                            .count() as i32;
1223                        let test_score = edges * aabr.size().product();
1224                        if best_score < test_score {
1225                            best_score = test_score;
1226                            best = Some(i);
1227                        }
1228                    }
1229                    if let Some(aabr) = best.map(|i| room.detail_areas.swap_remove(i)) {
1230                        room.details.push(Detail::Stage { aabr })
1231                    }
1232                    room.detail_areas.retain(|&aabr| {
1233                        if aabr.size().reduce_max() > 1 && rng.gen_bool(0.8) {
1234                            room.details.push(table(aabr.center(), aabr));
1235                            false
1236                        } else {
1237                            true
1238                        }
1239                    });
1240                },
1241                RoomKind::Bar => {
1242                    let mut best = None;
1243                    let mut best_score = 0;
1244                    for (i, aabr) in room.detail_areas.iter().enumerate() {
1245                        let test_score = Dir::iter()
1246                            .any(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1247                            as i32
1248                            * aabr.size().product();
1249                        if best_score < test_score {
1250                            best_score = test_score;
1251                            best = Some(i);
1252                        }
1253                    }
1254                    if let Some(aabr) = best.map(|i| room.detail_areas.swap_remove(i)) {
1255                        room.details.push(Detail::Bar { aabr })
1256                    }
1257                    room.detail_areas.retain(|&aabr| {
1258                        if aabr.size().reduce_max() > 1 && rng.gen_bool(0.1) {
1259                            room.details.push(table(aabr.center(), aabr));
1260                            false
1261                        } else {
1262                            true
1263                        }
1264                    });
1265                },
1266                RoomKind::Entrance => {},
1267            }
1268        }
1269
1270        Self {
1271            name,
1272            rooms,
1273            walls,
1274            roofs,
1275            door_tile,
1276            door_wpos,
1277            bounds,
1278        }
1279    }
1280}
1281
1282fn aabb(mut aabb: Aabb<i32>) -> Aabb<i32> {
1283    aabb.make_valid();
1284    aabb.max += 1;
1285    aabb
1286}
1287
1288impl Structure for Tavern {
1289    #[cfg(feature = "use-dyn-lib")]
1290    const UPDATE_FN: &'static [u8] = b"render_tavern\0";
1291
1292    #[cfg_attr(feature = "be-dyn-lib", unsafe(export_name = "render_tavern"))]
1293    fn render_inner(&self, _site: &Site, land: &Land, painter: &crate::site::Painter) {
1294        let field = RandomField::new(740384);
1295        let field_choose = RandomField::new(134598);
1296
1297        const DOWN: i32 = 6;
1298
1299        let mut offset = 0;
1300        let mut choose = |slice: &[Rgb<u8>]| -> Rgb<u8> {
1301            offset += 1;
1302            *field
1303                .choose(self.door_wpos + offset, slice)
1304                .expect("Color slice should not be empty.")
1305        };
1306
1307        let detail_fill = Fill::Brick(
1308            BlockKind::Rock,
1309            choose(&[
1310                Rgb::new(55, 65, 64),
1311                Rgb::new(46, 62, 100),
1312                Rgb::new(46, 100, 62),
1313                Rgb::new(100, 100, 105),
1314            ]),
1315            15,
1316        );
1317        let wall_fill = Fill::Brick(
1318            BlockKind::Wood,
1319            choose(&[
1320                Rgb::new(160, 53, 34),
1321                Rgb::new(147, 51, 29),
1322                Rgb::new(147, 101, 69),
1323                Rgb::new(90, 90, 95),
1324                Rgb::new(170, 140, 52),
1325            ]),
1326            20,
1327        );
1328        let wall_detail_fill = Fill::Brick(
1329            BlockKind::Wood,
1330            choose(&[Rgb::new(108, 100, 79), Rgb::new(150, 150, 150)]),
1331            25,
1332        );
1333        let floor_fill = Fill::Brick(
1334            BlockKind::Wood,
1335            choose(&[Rgb::new(42, 44, 43), Rgb::new(56, 18, 10)]),
1336            10,
1337        );
1338        let roof_fill = Fill::Brick(
1339            BlockKind::Wood,
1340            choose(&[
1341                Rgb::new(21, 43, 48),
1342                Rgb::new(11, 23, 38),
1343                Rgb::new(45, 28, 21),
1344                Rgb::new(10, 55, 40),
1345                Rgb::new(5, 35, 15),
1346                Rgb::new(40, 5, 11),
1347                Rgb::new(55, 45, 11),
1348            ]),
1349            20,
1350        );
1351        let simple_roof_fill = Fill::Brick(
1352            BlockKind::Wood,
1353            choose(&[Rgb::new(106, 73, 64), Rgb::new(85, 52, 43)]),
1354            20,
1355        );
1356
1357        let get_kind = |room| self.rooms.get(room).kind;
1358        let get_door_stair = |wall: &Wall, door: Aabr<i32>| {
1359            let filter = |room: &Id<Room>| self.rooms[*room].bounds.min.z > wall.base_alt;
1360            wall.to
1361                .filter(filter)
1362                .zip(Some(wall.to_dir))
1363                .or(wall.from.filter(filter).zip(Some(-wall.to_dir)))
1364                .map(|(room, to_dir)| {
1365                    let room = &self.rooms[room];
1366
1367                    let max = door.max + to_dir.to_vec2() * (room.bounds.min.z - wall.base_alt + 1);
1368                    (door.min, max, room, to_dir)
1369                })
1370        };
1371
1372        // Fill roofs
1373        for roof in self.roofs.values() {
1374            match roof.style {
1375                RoofStyle::Flat => {
1376                    painter
1377                        .aabb(aabb(Aabb {
1378                            min: roof.bounds.min.with_z(roof.min_z),
1379                            max: roof.bounds.max.with_z(roof.min_z),
1380                        }))
1381                        .fill(roof_fill.clone());
1382                },
1383                RoofStyle::Floor => {
1384                    painter
1385                        .aabb(aabb(Aabb {
1386                            min: roof.bounds.min.with_z(roof.min_z),
1387                            max: roof.bounds.max.with_z(roof.min_z),
1388                        }))
1389                        .fill(floor_fill.clone());
1390                },
1391                RoofStyle::FlatBars { dir } => painter
1392                    .aabb(aabb(Aabb {
1393                        min: dir
1394                            .select_aabr_with(roof.bounds, roof.bounds.min)
1395                            .with_z(roof.min_z),
1396                        max: dir
1397                            .select_aabr_with(roof.bounds, roof.bounds.max)
1398                            .with_z(roof.min_z),
1399                    }))
1400                    .repeat(
1401                        -dir.to_vec3() * 2,
1402                        (dir.select(roof.bounds.size()) as u32 + 3) / 2,
1403                    )
1404                    .fill(simple_roof_fill.clone()),
1405                RoofStyle::LeanTo { dir, max_z } => {
1406                    painter
1407                        .aabb(aabb(Aabb {
1408                            min: roof.bounds.min.with_z(roof.min_z),
1409                            max: roof.bounds.max.with_z(roof.min_z),
1410                        }))
1411                        .fill(roof_fill.clone());
1412                    painter
1413                        .ramp(
1414                            aabb(Aabb {
1415                                min: roof.bounds.min.with_z(roof.min_z),
1416                                max: roof.bounds.max.with_z(max_z),
1417                            }),
1418                            dir,
1419                        )
1420                        .fill(roof_fill.clone());
1421                    for d in [dir.orthogonal(), -dir.orthogonal()] {
1422                        painter
1423                            .ramp(
1424                                aabb(Aabb {
1425                                    min: (d.select_aabr_with(roof.bounds, roof.bounds.min)
1426                                        - d.to_vec2())
1427                                    .with_z(roof.min_z - 1),
1428                                    max: (d.select_aabr_with(roof.bounds, roof.bounds.max)
1429                                        - d.to_vec2())
1430                                    .with_z(max_z - 1),
1431                                }),
1432                                dir,
1433                            )
1434                            .fill(wall_fill.clone());
1435                        painter
1436                            .ramp(
1437                                aabb(Aabb {
1438                                    min: d
1439                                        .select_aabr_with(roof.bounds, roof.bounds.min)
1440                                        .with_z(roof.min_z - 1),
1441                                    max: d
1442                                        .select_aabr_with(roof.bounds, roof.bounds.max)
1443                                        .with_z(max_z - 1),
1444                                }),
1445                                dir,
1446                            )
1447                            .clear();
1448                    }
1449                },
1450                RoofStyle::Gable { dir, max_z } => {
1451                    painter
1452                        .gable(
1453                            aabb(Aabb {
1454                                min: roof.bounds.min.with_z(roof.min_z),
1455                                max: roof.bounds.max.with_z(max_z),
1456                            }),
1457                            max_z - roof.min_z + 1,
1458                            dir,
1459                        )
1460                        .fill(roof_fill.clone());
1461                    for dir in [dir, -dir] {
1462                        painter
1463                            .gable(
1464                                aabb(Aabb {
1465                                    min: (dir.select_aabr_with(roof.bounds, roof.bounds.min + 1)
1466                                        - dir.to_vec2())
1467                                    .with_z(roof.min_z),
1468                                    max: (dir.select_aabr_with(roof.bounds, roof.bounds.max - 1)
1469                                        - dir.to_vec2())
1470                                    .with_z(max_z - 1),
1471                                }),
1472                                max_z - roof.min_z,
1473                                dir,
1474                            )
1475                            .fill(wall_fill.clone());
1476                        painter
1477                            .aabb(aabb(Aabb {
1478                                min: (dir.select_aabr_with(roof.bounds, roof.bounds.min + 1)
1479                                    - dir.to_vec2())
1480                                .with_z(roof.min_z),
1481                                max: (dir.select_aabr_with(roof.bounds, roof.bounds.max - 1)
1482                                    - dir.to_vec2())
1483                                .with_z(roof.min_z),
1484                            }))
1485                            .fill(wall_detail_fill.clone());
1486                        let center_bounds = Aabr {
1487                            min: (dir.select_aabr_with(roof.bounds, roof.bounds.center())
1488                                - dir.to_vec2()),
1489                            max: (dir.select_aabr_with(
1490                                roof.bounds,
1491                                (roof.bounds.min + roof.bounds.max + 1) / 2,
1492                            ) - dir.to_vec2()),
1493                        };
1494                        painter
1495                            .aabb(aabb(Aabb {
1496                                min: center_bounds.min.with_z(roof.min_z),
1497                                max: center_bounds.max.with_z(max_z - 1),
1498                            }))
1499                            .fill(wall_detail_fill.clone());
1500                        for d in [dir.orthogonal(), -dir.orthogonal()] {
1501                            let hgt = max_z - roof.min_z;
1502                            let half_size = d.select(roof.bounds.size() + 1) / 2;
1503                            let e = half_size - hgt + 1;
1504                            let e = e - e % 2;
1505                            let f = half_size - e;
1506                            let hgt = (hgt - 1).min(e - f % 2) - (d.signum() - 1) / 2;
1507                            let mut aabr = Aabr {
1508                                min: d.select_aabr_with(center_bounds, center_bounds.min),
1509                                max: d.select_aabr_with(center_bounds, center_bounds.max)
1510                                    + d.to_vec2() * hgt,
1511                            }
1512                            .made_valid();
1513                            aabr.max += 1;
1514                            painter
1515                                .plane(
1516                                    aabr,
1517                                    aabr.min
1518                                        .with_z(if d.signum() < 0 {
1519                                            roof.min_z + hgt
1520                                        } else {
1521                                            roof.min_z
1522                                        })
1523                                        .as_(),
1524                                    d.to_vec2().as_(),
1525                                )
1526                                .fill(wall_detail_fill.clone());
1527                        }
1528                        painter
1529                            .gable(
1530                                aabb(Aabb {
1531                                    min: dir
1532                                        .select_aabr_with(roof.bounds, roof.bounds.min + 1)
1533                                        .with_z(roof.min_z),
1534                                    max: dir
1535                                        .select_aabr_with(roof.bounds, roof.bounds.max - 1)
1536                                        .with_z(max_z - 1),
1537                                }),
1538                                max_z - roof.min_z,
1539                                dir,
1540                            )
1541                            .clear();
1542                    }
1543                },
1544                RoofStyle::Hip { max_z } => {
1545                    painter
1546                        .pyramid(aabb(Aabb {
1547                            min: roof.bounds.min.with_z(roof.min_z),
1548                            max: roof.bounds.max.with_z(max_z),
1549                        }))
1550                        .fill(roof_fill.clone());
1551                },
1552            }
1553        }
1554
1555        // Fill floors
1556        for room in self.rooms.values() {
1557            painter
1558                .aabb(aabb(Aabb {
1559                    min: room.bounds.min.with_z(room.bounds.min.z - DOWN),
1560                    max: room.bounds.max.with_z(room.bounds.min.z - 1),
1561                }))
1562                .fill(floor_fill.clone());
1563        }
1564        // Fill walls
1565        for wall in self.walls.values() {
1566            let wall_aabb = Aabb {
1567                min: wall.start.with_z(wall.base_alt),
1568                max: wall.end.with_z(wall.top_alt),
1569            };
1570            let wall_dir = Dir::from_vec2(wall.end - wall.start);
1571            match (wall.from.map(get_kind), wall.to.map(get_kind)) {
1572                (Some(RoomKind::Garden), None) | (None, Some(RoomKind::Garden)) => {
1573                    let hgt = wall_aabb.min.z..=wall_aabb.max.z;
1574                    painter
1575                        .column(wall_aabb.min.xy(), hgt.clone())
1576                        .fill(wall_detail_fill.clone());
1577                    painter
1578                        .column(wall_aabb.max.xy(), hgt)
1579                        .fill(wall_detail_fill.clone());
1580                    let z = (wall.base_alt + wall.top_alt) / 2;
1581
1582                    painter
1583                        .aabb(aabb(Aabb {
1584                            min: (wall_aabb.min + wall_dir.to_vec2()).with_z(wall_aabb.min.z + 1),
1585                            max: (wall_aabb.max - wall_dir.to_vec2()).with_z(wall_aabb.max.z - 1),
1586                        }))
1587                        .clear();
1588
1589                    painter.rotated_sprite(
1590                        wall_aabb.min.with_z(z) + wall_dir.to_vec2(),
1591                        SpriteKind::WallSconce,
1592                        wall_dir.sprite_ori_legacy(),
1593                    );
1594                    painter.rotated_sprite(
1595                        wall_aabb.max.with_z(z) - wall_dir.to_vec2(),
1596                        SpriteKind::WallSconce,
1597                        wall_dir.opposite().sprite_ori_legacy(),
1598                    );
1599                    painter
1600                        .aabb(aabb(Aabb {
1601                            min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN),
1602                            max: wall_aabb.max.with_z(wall_aabb.min.z),
1603                        }))
1604                        .fill(wall_detail_fill.clone());
1605                    painter
1606                        .aabb(aabb(Aabb {
1607                            min: wall_aabb.min.with_z(wall_aabb.max.z),
1608                            max: wall_aabb.max,
1609                        }))
1610                        .fill(wall_detail_fill.clone());
1611                },
1612                (Some(RoomKind::Garden), Some(RoomKind::Garden)) => {
1613                    painter
1614                        .aabb(aabb(Aabb {
1615                            min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN),
1616                            max: wall_aabb.max.with_z(wall_aabb.min.z - 1),
1617                        }))
1618                        .fill(floor_fill.clone());
1619                    painter.aabb(aabb(wall_aabb)).clear();
1620                },
1621                (None, None) => {},
1622                _ => {
1623                    painter
1624                        .aabb(aabb(Aabb {
1625                            min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN),
1626                            max: wall_aabb.max,
1627                        }))
1628                        .fill(wall_fill.clone());
1629                    painter
1630                        .column(wall.start, wall.base_alt - DOWN..=wall.top_alt)
1631                        .fill(wall_detail_fill.clone());
1632                    painter
1633                        .column(wall.end, wall.base_alt - DOWN..=wall.top_alt)
1634                        .fill(wall_detail_fill.clone());
1635                },
1636            }
1637            if let Some(door) = wall.door_bounds() {
1638                let orth = wall.to_dir.orthogonal();
1639                if let Some((min, max, room, to_dir)) = get_door_stair(wall, door) {
1640                    painter
1641                        .aabb(aabb(Aabb {
1642                            min: (min + to_dir.to_vec2() - orth.to_vec2())
1643                                .with_z(wall.base_alt - 1),
1644                            max: (max + orth.to_vec2()).with_z(room.bounds.min.z - 1),
1645                        }))
1646                        .fill(floor_fill.clone());
1647                }
1648            }
1649        }
1650
1651        // Add details per room
1652        for room in self.rooms.values() {
1653            painter.aabb(aabb(room.bounds)).clear();
1654
1655            let room_aabr = Aabr {
1656                min: room.bounds.min.xy(),
1657                max: room.bounds.max.xy(),
1658            };
1659            match room.kind {
1660                RoomKind::Garden => {},
1661                RoomKind::Cellar => {
1662                    for aabr in room.detail_areas.iter().copied() {
1663                        for dir in Dir::iter()
1664                            .filter(|dir| dir.select_aabr(aabr) == dir.select_aabr(room_aabr))
1665                        {
1666                            let pos = dir
1667                                .select_aabr_with(aabr, aabr.center())
1668                                .with_z(room.bounds.center().z + 1);
1669
1670                            painter.rotated_sprite(
1671                                pos,
1672                                SpriteKind::WallLampSmall,
1673                                dir.opposite().sprite_ori_legacy(),
1674                            );
1675
1676                            for x in dir.orthogonal().select(aabr.min)
1677                                ..=dir.orthogonal().select(aabr.max)
1678                            {
1679                                let pos = dir.select_aabr_with(aabr, x).with_z(room.bounds.min.z);
1680                                if field.chance(pos, 0.3) {
1681                                    let sprite = field_choose
1682                                        .choose(pos, &[
1683                                            SpriteKind::Crate,
1684                                            SpriteKind::Barrel,
1685                                            SpriteKind::BarrelWoodWater,
1686                                        ])
1687                                        .unwrap();
1688                                    painter.owned_resource_sprite(pos, *sprite, 0);
1689                                }
1690                            }
1691                        }
1692                    }
1693                },
1694                RoomKind::Stage => {
1695                    for aabr in room.detail_areas.iter().copied() {
1696                        for dir in Dir::iter().filter(|dir| {
1697                            dir.select_aabr(aabr) == dir.select_aabr(room_aabr)
1698                                && dir.rotated_cw().select_aabr(aabr)
1699                                    == dir.rotated_cw().select_aabr(room_aabr)
1700                        }) {
1701                            let pos = dir.select_aabr_with(
1702                                aabr,
1703                                Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)),
1704                            );
1705                            painter.sprite(pos.with_z(room.bounds.min.z), SpriteKind::StreetLamp);
1706                        }
1707                    }
1708                },
1709                RoomKind::Bar | RoomKind::Seating => {
1710                    for aabr in room.detail_areas.iter().copied() {
1711                        for dir in Dir::iter()
1712                            .filter(|dir| dir.select_aabr(aabr) == dir.select_aabr(room_aabr))
1713                        {
1714                            let pos = dir
1715                                .select_aabr_with(aabr, aabr.center())
1716                                .with_z(room.bounds.center().z);
1717                            let orth = dir.orthogonal();
1718                            if room.walls[dir].iter().any(|wall| {
1719                                let wall = &self.walls[*wall];
1720                                (orth.select(wall.start)..=orth.select(wall.end))
1721                                    .contains(&orth.select(pos))
1722                                    && (wall.from.is_none() || wall.to.is_none())
1723                            }) {
1724                                continue;
1725                            }
1726
1727                            painter.rotated_sprite(
1728                                pos,
1729                                SpriteKind::WallLampSmall,
1730                                dir.opposite().sprite_ori_legacy(),
1731                            );
1732                        }
1733                    }
1734                },
1735                RoomKind::Entrance => {
1736                    for aabr in room.detail_areas.iter() {
1737                        let edges = Dir::iter()
1738                            .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1739                            .count();
1740                        let hanger_pos = if edges == 2 {
1741                            let pos = aabr.center().with_z(room.bounds.min.z);
1742                            painter.sprite(pos, SpriteKind::CoatrackMetalWoodland);
1743                            Some(pos)
1744                        } else {
1745                            None
1746                        };
1747
1748                        for dir in Dir::iter()
1749                            .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1750                        {
1751                            let pos = dir
1752                                .select_aabr_with(*aabr, aabr.center())
1753                                .with_z(room.bounds.center().z + 1);
1754                            if hanger_pos.is_some_and(|p| p.xy() != pos.xy()) {
1755                                painter.rotated_sprite(
1756                                    pos,
1757                                    SpriteKind::WallLampSmall,
1758                                    dir.opposite().sprite_ori_legacy(),
1759                                );
1760                            }
1761                        }
1762                    }
1763                },
1764            }
1765            for detail in room.details.iter() {
1766                match *detail {
1767                    Detail::Bar { aabr } => {
1768                        for dir in Dir::iter() {
1769                            let edge = dir.select_aabr(aabr);
1770                            let rot_dir = if field.chance(aabr.center().with_z(0), 0.5) {
1771                                dir.rotated_cw()
1772                            } else {
1773                                dir.rotated_ccw()
1774                            };
1775                            let rot_edge = rot_dir.select_aabr(aabr);
1776                            match (
1777                                edge == dir.select_aabr(room_aabr),
1778                                rot_edge == rot_dir.select_aabr(room_aabr),
1779                            ) {
1780                                (false, _) => {
1781                                    let (min, max) = (
1782                                        dir.select_aabr_with(
1783                                            aabr,
1784                                            Vec2::broadcast(rot_dir.select_aabr(aabr)),
1785                                        ),
1786                                        dir.select_aabr_with(
1787                                            aabr,
1788                                            Vec2::broadcast(rot_dir.opposite().select_aabr(aabr)),
1789                                        ),
1790                                    );
1791                                    painter
1792                                        .aabb(aabb(Aabb {
1793                                            min: (min - rot_dir.to_vec2())
1794                                                .with_z(room.bounds.min.z),
1795                                            max: max.with_z(room.bounds.min.z),
1796                                        }))
1797                                        .fill(wall_detail_fill.clone());
1798                                    painter
1799                                        .aabb(aabb(Aabb {
1800                                            min: min.with_z(room.bounds.min.z + 3),
1801                                            max: max.with_z(room.bounds.max.z),
1802                                        }))
1803                                        .fill(wall_detail_fill.clone());
1804                                },
1805                                (true, true) => {
1806                                    painter.sprite(
1807                                        dir.abs().vec2(edge, rot_edge).with_z(room.bounds.min.z),
1808                                        SpriteKind::CookingPot,
1809                                    );
1810                                },
1811                                (true, false) => {},
1812                            }
1813                        }
1814                    },
1815                    Detail::Stage { aabr } => {
1816                        painter
1817                            .aabb(aabb(Aabb {
1818                                min: aabr.min.with_z(room.bounds.min.z),
1819                                max: aabr.max.with_z(room.bounds.min.z),
1820                            }))
1821                            .fill(detail_fill.clone());
1822                        painter
1823                            .aabb(aabb(Aabb {
1824                                min: (aabr.min + 1).with_z(room.bounds.min.z),
1825                                max: (aabr.max - 1).with_z(room.bounds.min.z),
1826                            }))
1827                            .fill(wall_fill.clone());
1828                        for dir in Dir::iter().filter(|dir| {
1829                            dir.select_aabr(aabr) != dir.select_aabr(room_aabr)
1830                                && dir.rotated_cw().select_aabr(aabr)
1831                                    != dir.rotated_cw().select_aabr(room_aabr)
1832                        }) {
1833                            let pos = dir.select_aabr_with(
1834                                aabr,
1835                                Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)),
1836                            );
1837                            painter
1838                                .column(pos, room.bounds.min.z..=room.bounds.max.z)
1839                                .fill(wall_detail_fill.clone());
1840
1841                            for dir in Dir::iter() {
1842                                painter.rotated_sprite(
1843                                    pos.with_z(room.bounds.center().z + 1) + dir.to_vec2(),
1844                                    SpriteKind::WallSconce,
1845                                    dir.sprite_ori_legacy(),
1846                                );
1847                            }
1848                        }
1849                    },
1850                    Detail::Table { pos, chairs } => {
1851                        let pos = pos.with_z(room.bounds.min.z);
1852                        painter.sprite(pos, SpriteKind::DiningtableWoodWoodlandSquare);
1853                        for dir in chairs.into_iter() {
1854                            painter.rotated_sprite(
1855                                pos + dir.to_vec2(),
1856                                SpriteKind::ChairWoodWoodland2,
1857                                dir.opposite().sprite_ori(),
1858                            );
1859                        }
1860                    },
1861                }
1862            }
1863        }
1864
1865        // Fill in wall details
1866        for wall in self.walls.values() {
1867            let kinds = (wall.from.map(get_kind), wall.to.map(get_kind));
1868            let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = kinds {
1869                let in_dir = if to.is_none() {
1870                    -wall.to_dir
1871                } else {
1872                    wall.to_dir
1873                };
1874
1875                Some((in_dir, room))
1876            } else {
1877                None
1878            };
1879            if let Some((in_dir, room)) = in_dir_room {
1880                let width = in_dir.orthogonal().select(wall.end - wall.start).abs();
1881                let wall_center = (wall.start + wall.end) / 2;
1882                let door_dist = wall.door_bounds().map_or(i32::MAX, |door| {
1883                    (door.min - wall_center)
1884                        .map(|x| x.abs())
1885                        .reduce_max()
1886                        .max((door.max - wall_center).map(|x| x.abs()).reduce_max())
1887                });
1888                match room {
1889                    RoomKind::Garden => {
1890                        if door_dist >= 2 {
1891                            painter.rotated_sprite(
1892                                wall_center.with_z(wall.base_alt + 1),
1893                                SpriteKind::Planter,
1894                                in_dir.sprite_ori_legacy(),
1895                            );
1896                        }
1897                    },
1898                    _ => {
1899                        if width >= 5
1900                            && door_dist > 3
1901                            && wall.base_alt >= land.get_alt_approx(wall_center) as i32
1902                        {
1903                            painter
1904                                .aabb(aabb(Aabb {
1905                                    min: (wall_center + in_dir.rotated_ccw().to_vec2())
1906                                        .with_z(wall.base_alt + 1),
1907                                    max: (wall_center + in_dir.rotated_cw().to_vec2())
1908                                        .with_z(wall.base_alt + 2),
1909                                }))
1910                                .fill(Fill::sprite_ori(
1911                                    SpriteKind::Window1,
1912                                    in_dir.sprite_ori_legacy(),
1913                                ));
1914                        }
1915                    },
1916                }
1917            }
1918            if let Some(door) = wall.door_bounds()
1919                && !matches!(kinds, (Some(RoomKind::Garden), Some(RoomKind::Garden)))
1920            {
1921                let orth = wall.to_dir.orthogonal();
1922                painter
1923                    .aabb(aabb(Aabb {
1924                        min: (door.min - orth.to_vec2()).with_z(wall.base_alt),
1925                        max: (door.max + orth.to_vec2()).with_z(wall.base_alt + 3),
1926                    }))
1927                    .fill(detail_fill.clone());
1928                painter
1929                    .aabb(aabb(Aabb {
1930                        min: (door.min - orth.to_vec2()).with_z(wall.base_alt - 1),
1931                        max: (door.max + orth.to_vec2()).with_z(wall.base_alt - 1),
1932                    }))
1933                    .fill(floor_fill.clone());
1934                painter
1935                    .aabb(aabb(Aabb {
1936                        min: (door.min + wall.to_dir.to_vec2()).with_z(wall.base_alt),
1937                        max: (door.max - wall.to_dir.to_vec2()).with_z(wall.base_alt + 2),
1938                    }))
1939                    .clear();
1940                if let Some((min, max, room, to_dir)) = get_door_stair(wall, door) {
1941                    // Place a ramp if the door is lower than the room alt.
1942                    painter
1943                        .ramp(
1944                            aabb(Aabb {
1945                                min: (min - to_dir.to_vec2() * 3).with_z(wall.base_alt),
1946                                max: max.with_z(room.bounds.min.z + 2),
1947                            }),
1948                            to_dir,
1949                        )
1950                        // TOOD: For zoomy worldgen, this a sheared aabb.
1951                        .without(
1952                            painter
1953                                .ramp(
1954                                    aabb(Aabb {
1955                                        min: (min + to_dir.to_vec2() * 2).with_z(wall.base_alt),
1956                                        max: max.with_z(room.bounds.min.z - 1),
1957                                    }),
1958                                    to_dir,
1959                                )
1960                        )
1961                        .clear();
1962                }
1963                if let Some((in_dir, _room)) = in_dir_room {
1964                    let sprite = match in_dir.rotated_cw().select(door.size()) {
1965                        2.. => SpriteKind::DoorWide,
1966                        _ => SpriteKind::Door,
1967                    };
1968                    painter.rotated_sprite(
1969                        in_dir
1970                            .rotated_cw()
1971                            .select_aabr_with(door, door.min)
1972                            .with_z(wall.base_alt),
1973                        sprite,
1974                        in_dir.sprite_ori_legacy(),
1975                    );
1976                    painter.rotated_sprite(
1977                        in_dir
1978                            .rotated_ccw()
1979                            .select_aabr_with(door, door.min)
1980                            .with_z(wall.base_alt),
1981                        sprite,
1982                        in_dir.opposite().sprite_ori_legacy(),
1983                    );
1984
1985                    let dir = match field.chance(door.min.with_z(wall.base_alt), 0.5) {
1986                        true => in_dir.rotated_cw(),
1987                        false => in_dir.rotated_ccw(),
1988                    };
1989
1990                    let pos =
1991                        dir.select_aabr_with(door, door.min) + dir.to_vec2() - in_dir.to_vec2();
1992
1993                    painter.rotated_sprite_with_cfg(
1994                        pos.with_z(wall.base_alt + 2),
1995                        SpriteKind::HangingSign,
1996                        in_dir.opposite().sprite_ori_legacy(),
1997                        SpriteCfg {
1998                            content: Some(Content::Plain(self.name.clone())),
1999                            ..Default::default()
2000                        },
2001                    );
2002                }
2003            }
2004        }
2005
2006        // Fill stairs
2007        for roof in self.roofs.values() {
2008            if let Some((stairs_aabb, dir)) = roof.stairs {
2009                painter
2010                    .aabb(aabb(dir.to_dir3().trim_aabb(
2011                        Aabb {
2012                            min: stairs_aabb.min.with_z(roof.min_z),
2013                            max: stairs_aabb.max.with_z(roof.min_z),
2014                        },
2015                        (dir.to_dir3().select(stairs_aabb.size()) - 4).max(0),
2016                    )))
2017                    .clear();
2018                painter
2019                    .ramp(aabb(stairs_aabb), dir)
2020                    .fill(floor_fill.clone());
2021                painter
2022                    .ramp(
2023                        aabb(Dir3::NegZ.trim_aabb(dir.to_dir3().trim_aabb(stairs_aabb, 1), 1)),
2024                        dir,
2025                    )
2026                    .clear();
2027            }
2028        }
2029    }
2030}