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