Skip to main content

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    util::Dir3,
9};
10use enum_map::EnumMap;
11use enumset::EnumSet;
12use hashbrown::HashSet;
13use rand::{
14    RngExt,
15    seq::{IndexedRandom, IteratorRandom},
16};
17use strum::{EnumIter, IntoEnumIterator};
18use vek::*;
19
20use crate::{
21    IndexRef, Land,
22    site::{Dir2, Fill, Site, SpawnRules, Structure, generation::PrimitiveTransform, namegen},
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: Dir2,
36    door: Option<(i32, i32)>,
37}
38
39impl Wall {
40    pub fn door_pos(&self) -> Option<Vec3<f32>> {
41        let wall_dir = Dir2::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 = Dir2::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: Dir2 },
66    LeanTo { dir: Dir2, max_z: i32 },
67    Gable { dir: Dir2, 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>, Dir2)>,
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<Dir2>,
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<Dir2, 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 RngExt,
285        site: &Site,
286        door_tile: Vec2<i32>,
287        door_dir: Dir2,
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 RngExt, range: RangeInclusive<i32>, snap_max: i32) -> i32 {
316            let res = rng.random_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: Dir2,
329            in_pos: Vec2<i32>,
330            rng: &mut impl RngExt,
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.random_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 RngExt,
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 = Dir2::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<Dir2>,
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.random());
426            let entrance_room_hgt = rng.random_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: Dir2::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.random_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(Dir2) -> 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 = Dir2::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.random_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.random());
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.random_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.random_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: Dir2::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.random_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.random());
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::<Dir2, Vec<_>>::default();
754            for dir in Dir2::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: Dir2, 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 Dir2::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 = Dir2::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.random_bool(0.8)
845                    {
846                        let door_center = rng.random_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(Dir2::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.random_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 = Dir2::X.select(roof_bounds.size()) as f32
952                        / Dir2::Y.select(roof_bounds.size()) as f32;
953                    valid_styles.extend([
954                        (5.0 * ratio, RoofStyle::FlatBars { dir: Dir2::X }),
955                        (5.0 / ratio, RoofStyle::FlatBars { dir: Dir2::Y }),
956                    ]);
957                }
958
959                // Find heights of possible adjecent rooms.
960                let mut dir_zs = EnumMap::default();
961                for dir in Dir2::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 [Dir2::X, Dir2::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 Dir2::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 Dir2::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 = Dir2::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.random()),
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: Dir2::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.random_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 = Dir2::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.random_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 = Dir2::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.random_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 = "dyn-lib")]
1291    #[unsafe(export_name = "as_dyn_structure_tavern")]
1292    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
1293        Some((Self::as_dyn_impl(self), "as_dyn_structure_tavern"))
1294    }
1295
1296    fn spawn_rules_inner(
1297        &self,
1298        spawn_rules: &mut SpawnRules,
1299        _land: &Land,
1300        _wpos: Vec2<i32>,
1301        weight: f32,
1302    ) {
1303        spawn_rules.prefer_alt(self.door_wpos.z as f32 - 1.0, weight * 2.0);
1304    }
1305
1306    fn door_tile(&self) -> Option<Vec2<i32>> { Some(self.door_tile) }
1307
1308    fn render_inner(&self, _site: &Site, land: &Land, painter: &crate::site::Painter) {
1309        let field = RandomField::new(740384);
1310        let field_choose = RandomField::new(134598);
1311
1312        const DOWN: i32 = 6;
1313
1314        let mut offset = 0;
1315        let mut choose = |slice: &[Rgb<u8>]| -> Rgb<u8> {
1316            offset += 1;
1317            *field
1318                .choose(self.door_wpos + offset, slice)
1319                .expect("Color slice should not be empty.")
1320        };
1321
1322        let detail_fill = Fill::Brick(
1323            BlockKind::Rock,
1324            choose(&[
1325                Rgb::new(55, 65, 64),
1326                Rgb::new(46, 62, 100),
1327                Rgb::new(46, 100, 62),
1328                Rgb::new(100, 100, 105),
1329            ]),
1330            15,
1331        );
1332        let wall_fill = Fill::Brick(
1333            BlockKind::Wood,
1334            choose(&[
1335                Rgb::new(160, 53, 34),
1336                Rgb::new(147, 51, 29),
1337                Rgb::new(147, 101, 69),
1338                Rgb::new(90, 90, 95),
1339                Rgb::new(170, 140, 52),
1340            ]),
1341            20,
1342        );
1343        let wall_detail_fill = Fill::Brick(
1344            BlockKind::Wood,
1345            choose(&[Rgb::new(108, 100, 79), Rgb::new(150, 150, 150)]),
1346            25,
1347        );
1348        let floor_fill = Fill::Brick(
1349            BlockKind::Wood,
1350            choose(&[Rgb::new(42, 44, 43), Rgb::new(56, 18, 10)]),
1351            10,
1352        );
1353        let roof_fill = Fill::Brick(
1354            BlockKind::Wood,
1355            choose(&[
1356                Rgb::new(21, 43, 48),
1357                Rgb::new(11, 23, 38),
1358                Rgb::new(45, 28, 21),
1359                Rgb::new(10, 55, 40),
1360                Rgb::new(5, 35, 15),
1361                Rgb::new(40, 5, 11),
1362                Rgb::new(55, 45, 11),
1363            ]),
1364            20,
1365        );
1366        let simple_roof_fill = Fill::Brick(
1367            BlockKind::Wood,
1368            choose(&[Rgb::new(106, 73, 64), Rgb::new(85, 52, 43)]),
1369            20,
1370        );
1371
1372        let get_kind = |room| self.rooms.get(room).kind;
1373        let get_door_stair = |wall: &Wall, door: Aabr<i32>| {
1374            let filter = |room: &Id<Room>| self.rooms[*room].bounds.min.z > wall.base_alt;
1375            wall.to
1376                .filter(filter)
1377                .zip(Some(wall.to_dir))
1378                .or(wall.from.filter(filter).zip(Some(-wall.to_dir)))
1379                .map(|(room, to_dir)| {
1380                    let room = &self.rooms[room];
1381
1382                    let max = door.max + to_dir.to_vec2() * (room.bounds.min.z - wall.base_alt + 1);
1383                    (door.min, max, room, to_dir)
1384                })
1385        };
1386
1387        // Fill roofs
1388        for roof in self.roofs.values() {
1389            match roof.style {
1390                RoofStyle::Flat => {
1391                    painter
1392                        .aabb(aabb(Aabb {
1393                            min: roof.bounds.min.with_z(roof.min_z),
1394                            max: roof.bounds.max.with_z(roof.min_z),
1395                        }))
1396                        .fill(roof_fill.clone());
1397                },
1398                RoofStyle::Floor => {
1399                    painter
1400                        .aabb(aabb(Aabb {
1401                            min: roof.bounds.min.with_z(roof.min_z),
1402                            max: roof.bounds.max.with_z(roof.min_z),
1403                        }))
1404                        .fill(floor_fill.clone());
1405                },
1406                RoofStyle::FlatBars { dir } => painter
1407                    .aabb(aabb(Aabb {
1408                        min: dir
1409                            .select_aabr_with(roof.bounds, roof.bounds.min)
1410                            .with_z(roof.min_z),
1411                        max: dir
1412                            .select_aabr_with(roof.bounds, roof.bounds.max)
1413                            .with_z(roof.min_z),
1414                    }))
1415                    .repeat(
1416                        -dir.to_vec3() * 2,
1417                        (dir.select(roof.bounds.size()) as u32 + 3) / 2,
1418                    )
1419                    .fill(simple_roof_fill.clone()),
1420                RoofStyle::LeanTo { dir, max_z } => {
1421                    painter
1422                        .aabb(aabb(Aabb {
1423                            min: roof.bounds.min.with_z(roof.min_z),
1424                            max: roof.bounds.max.with_z(roof.min_z),
1425                        }))
1426                        .fill(roof_fill.clone());
1427                    painter
1428                        .ramp(
1429                            aabb(Aabb {
1430                                min: roof.bounds.min.with_z(roof.min_z),
1431                                max: roof.bounds.max.with_z(max_z),
1432                            }),
1433                            dir,
1434                        )
1435                        .fill(roof_fill.clone());
1436                    for d in [dir.orthogonal(), -dir.orthogonal()] {
1437                        painter
1438                            .ramp(
1439                                aabb(Aabb {
1440                                    min: (d.select_aabr_with(roof.bounds, roof.bounds.min)
1441                                        - d.to_vec2())
1442                                    .with_z(roof.min_z - 1),
1443                                    max: (d.select_aabr_with(roof.bounds, roof.bounds.max)
1444                                        - d.to_vec2())
1445                                    .with_z(max_z - 1),
1446                                }),
1447                                dir,
1448                            )
1449                            .fill(wall_fill.clone());
1450                        painter
1451                            .ramp(
1452                                aabb(Aabb {
1453                                    min: d
1454                                        .select_aabr_with(roof.bounds, roof.bounds.min)
1455                                        .with_z(roof.min_z - 1),
1456                                    max: d
1457                                        .select_aabr_with(roof.bounds, roof.bounds.max)
1458                                        .with_z(max_z - 1),
1459                                }),
1460                                dir,
1461                            )
1462                            .clear();
1463                    }
1464                },
1465                RoofStyle::Gable { dir, max_z } => {
1466                    painter
1467                        .gable(
1468                            aabb(Aabb {
1469                                min: roof.bounds.min.with_z(roof.min_z),
1470                                max: roof.bounds.max.with_z(max_z),
1471                            }),
1472                            max_z - roof.min_z + 1,
1473                            dir,
1474                        )
1475                        .fill(roof_fill.clone());
1476                    for dir in [dir, -dir] {
1477                        painter
1478                            .gable(
1479                                aabb(Aabb {
1480                                    min: (dir.select_aabr_with(roof.bounds, roof.bounds.min + 1)
1481                                        - dir.to_vec2())
1482                                    .with_z(roof.min_z),
1483                                    max: (dir.select_aabr_with(roof.bounds, roof.bounds.max - 1)
1484                                        - dir.to_vec2())
1485                                    .with_z(max_z - 1),
1486                                }),
1487                                max_z - roof.min_z,
1488                                dir,
1489                            )
1490                            .fill(wall_fill.clone());
1491                        painter
1492                            .aabb(aabb(Aabb {
1493                                min: (dir.select_aabr_with(roof.bounds, roof.bounds.min + 1)
1494                                    - dir.to_vec2())
1495                                .with_z(roof.min_z),
1496                                max: (dir.select_aabr_with(roof.bounds, roof.bounds.max - 1)
1497                                    - dir.to_vec2())
1498                                .with_z(roof.min_z),
1499                            }))
1500                            .fill(wall_detail_fill.clone());
1501                        let center_bounds = Aabr {
1502                            min: (dir.select_aabr_with(roof.bounds, roof.bounds.center())
1503                                - dir.to_vec2()),
1504                            max: (dir.select_aabr_with(
1505                                roof.bounds,
1506                                (roof.bounds.min + roof.bounds.max + 1) / 2,
1507                            ) - dir.to_vec2()),
1508                        };
1509                        painter
1510                            .aabb(aabb(Aabb {
1511                                min: center_bounds.min.with_z(roof.min_z),
1512                                max: center_bounds.max.with_z(max_z - 1),
1513                            }))
1514                            .fill(wall_detail_fill.clone());
1515                        for d in [dir.orthogonal(), -dir.orthogonal()] {
1516                            let hgt = max_z - roof.min_z;
1517                            let half_size = d.select(roof.bounds.size() + 1) / 2;
1518                            let e = half_size - hgt + 1;
1519                            let e = e - e % 2;
1520                            let f = half_size - e;
1521                            let hgt = (hgt - 1).min(e - f % 2) - (d.signum() - 1) / 2;
1522                            let mut aabr = Aabr {
1523                                min: d.select_aabr_with(center_bounds, center_bounds.min),
1524                                max: d.select_aabr_with(center_bounds, center_bounds.max)
1525                                    + d.to_vec2() * hgt,
1526                            }
1527                            .made_valid();
1528                            aabr.max += 1;
1529                            painter
1530                                .plane(
1531                                    aabr,
1532                                    aabr.min
1533                                        .with_z(if d.signum() < 0 {
1534                                            roof.min_z + hgt
1535                                        } else {
1536                                            roof.min_z
1537                                        })
1538                                        .as_(),
1539                                    d.to_vec2().as_(),
1540                                )
1541                                .fill(wall_detail_fill.clone());
1542                        }
1543                        painter
1544                            .gable(
1545                                aabb(Aabb {
1546                                    min: dir
1547                                        .select_aabr_with(roof.bounds, roof.bounds.min + 1)
1548                                        .with_z(roof.min_z),
1549                                    max: dir
1550                                        .select_aabr_with(roof.bounds, roof.bounds.max - 1)
1551                                        .with_z(max_z - 1),
1552                                }),
1553                                max_z - roof.min_z,
1554                                dir,
1555                            )
1556                            .clear();
1557                    }
1558                },
1559                RoofStyle::Hip { max_z } => {
1560                    painter
1561                        .pyramid(aabb(Aabb {
1562                            min: roof.bounds.min.with_z(roof.min_z),
1563                            max: roof.bounds.max.with_z(max_z),
1564                        }))
1565                        .fill(roof_fill.clone());
1566                },
1567            }
1568        }
1569
1570        // Fill floors
1571        for room in self.rooms.values() {
1572            painter
1573                .aabb(aabb(Aabb {
1574                    min: room.bounds.min.with_z(room.bounds.min.z - DOWN),
1575                    max: room.bounds.max.with_z(room.bounds.min.z - 1),
1576                }))
1577                .fill(floor_fill.clone());
1578        }
1579        // Fill walls
1580        for wall in self.walls.values() {
1581            let wall_aabb = Aabb {
1582                min: wall.start.with_z(wall.base_alt),
1583                max: wall.end.with_z(wall.top_alt),
1584            };
1585            let wall_dir = Dir2::from_vec2(wall.end - wall.start);
1586            match (wall.from.map(get_kind), wall.to.map(get_kind)) {
1587                (Some(RoomKind::Garden), None) | (None, Some(RoomKind::Garden)) => {
1588                    let hgt = wall_aabb.min.z..=wall_aabb.max.z;
1589                    painter
1590                        .column(wall_aabb.min.xy(), hgt.clone())
1591                        .fill(wall_detail_fill.clone());
1592                    painter
1593                        .column(wall_aabb.max.xy(), hgt)
1594                        .fill(wall_detail_fill.clone());
1595                    let z = (wall.base_alt + wall.top_alt) / 2;
1596
1597                    painter
1598                        .aabb(aabb(Aabb {
1599                            min: (wall_aabb.min + wall_dir.to_vec2()).with_z(wall_aabb.min.z + 1),
1600                            max: (wall_aabb.max - wall_dir.to_vec2()).with_z(wall_aabb.max.z - 1),
1601                        }))
1602                        .clear();
1603
1604                    painter.rotated_sprite(
1605                        wall_aabb.min.with_z(z) + wall_dir.to_vec2(),
1606                        SpriteKind::WallSconce,
1607                        wall_dir.sprite_ori_legacy(),
1608                    );
1609                    painter.rotated_sprite(
1610                        wall_aabb.max.with_z(z) - wall_dir.to_vec2(),
1611                        SpriteKind::WallSconce,
1612                        wall_dir.opposite().sprite_ori_legacy(),
1613                    );
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),
1618                        }))
1619                        .fill(wall_detail_fill.clone());
1620                    painter
1621                        .aabb(aabb(Aabb {
1622                            min: wall_aabb.min.with_z(wall_aabb.max.z),
1623                            max: wall_aabb.max,
1624                        }))
1625                        .fill(wall_detail_fill.clone());
1626                },
1627                (Some(RoomKind::Garden), Some(RoomKind::Garden)) => {
1628                    painter
1629                        .aabb(aabb(Aabb {
1630                            min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN),
1631                            max: wall_aabb.max.with_z(wall_aabb.min.z - 1),
1632                        }))
1633                        .fill(floor_fill.clone());
1634                    painter.aabb(aabb(wall_aabb)).clear();
1635                },
1636                (None, None) => {},
1637                _ => {
1638                    painter
1639                        .aabb(aabb(Aabb {
1640                            min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN),
1641                            max: wall_aabb.max,
1642                        }))
1643                        .fill(wall_fill.clone());
1644                    painter
1645                        .column(wall.start, wall.base_alt - DOWN..=wall.top_alt)
1646                        .fill(wall_detail_fill.clone());
1647                    painter
1648                        .column(wall.end, wall.base_alt - DOWN..=wall.top_alt)
1649                        .fill(wall_detail_fill.clone());
1650                },
1651            }
1652            if let Some(door) = wall.door_bounds() {
1653                let orth = wall.to_dir.orthogonal();
1654                if let Some((min, max, room, to_dir)) = get_door_stair(wall, door) {
1655                    painter
1656                        .aabb(aabb(Aabb {
1657                            min: (min + to_dir.to_vec2() - orth.to_vec2())
1658                                .with_z(wall.base_alt - 1),
1659                            max: (max + orth.to_vec2()).with_z(room.bounds.min.z - 1),
1660                        }))
1661                        .fill(floor_fill.clone());
1662                }
1663            }
1664        }
1665
1666        // Add details per room
1667        for room in self.rooms.values() {
1668            painter.aabb(aabb(room.bounds)).clear();
1669
1670            let room_aabr = Aabr {
1671                min: room.bounds.min.xy(),
1672                max: room.bounds.max.xy(),
1673            };
1674            match room.kind {
1675                RoomKind::Garden => {},
1676                RoomKind::Cellar => {
1677                    for aabr in room.detail_areas.iter().copied() {
1678                        for dir in Dir2::iter()
1679                            .filter(|dir| dir.select_aabr(aabr) == dir.select_aabr(room_aabr))
1680                        {
1681                            let pos = dir
1682                                .select_aabr_with(aabr, aabr.center())
1683                                .with_z(room.bounds.center().z + 1);
1684
1685                            painter.rotated_sprite(
1686                                pos,
1687                                SpriteKind::WallLampSmall,
1688                                dir.opposite().sprite_ori_legacy(),
1689                            );
1690
1691                            for x in dir.orthogonal().select(aabr.min)
1692                                ..=dir.orthogonal().select(aabr.max)
1693                            {
1694                                let pos = dir.select_aabr_with(aabr, x).with_z(room.bounds.min.z);
1695                                if field.chance(pos, 0.3) {
1696                                    let sprite = field_choose
1697                                        .choose(pos, &[
1698                                            SpriteKind::Crate,
1699                                            SpriteKind::Barrel,
1700                                            SpriteKind::BarrelWoodWater,
1701                                        ])
1702                                        .unwrap();
1703                                    painter.owned_resource_sprite(pos, *sprite, 0);
1704                                }
1705                            }
1706                        }
1707                    }
1708                },
1709                RoomKind::Stage => {
1710                    for aabr in room.detail_areas.iter().copied() {
1711                        for dir in Dir2::iter().filter(|dir| {
1712                            dir.select_aabr(aabr) == dir.select_aabr(room_aabr)
1713                                && dir.rotated_cw().select_aabr(aabr)
1714                                    == dir.rotated_cw().select_aabr(room_aabr)
1715                        }) {
1716                            let pos = dir.select_aabr_with(
1717                                aabr,
1718                                Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)),
1719                            );
1720                            painter.sprite(pos.with_z(room.bounds.min.z), SpriteKind::StreetLamp);
1721                        }
1722                    }
1723                },
1724                RoomKind::Bar | RoomKind::Seating => {
1725                    for aabr in room.detail_areas.iter().copied() {
1726                        for dir in Dir2::iter()
1727                            .filter(|dir| dir.select_aabr(aabr) == dir.select_aabr(room_aabr))
1728                        {
1729                            let pos = dir
1730                                .select_aabr_with(aabr, aabr.center())
1731                                .with_z(room.bounds.center().z);
1732                            let orth = dir.orthogonal();
1733                            if room.walls[dir].iter().any(|wall| {
1734                                let wall = &self.walls[*wall];
1735                                (orth.select(wall.start)..=orth.select(wall.end))
1736                                    .contains(&orth.select(pos))
1737                                    && (wall.from.is_none() || wall.to.is_none())
1738                            }) {
1739                                continue;
1740                            }
1741
1742                            painter.rotated_sprite(
1743                                pos,
1744                                SpriteKind::WallLampSmall,
1745                                dir.opposite().sprite_ori_legacy(),
1746                            );
1747                        }
1748                    }
1749                },
1750                RoomKind::Entrance => {
1751                    for aabr in room.detail_areas.iter() {
1752                        let edges = Dir2::iter()
1753                            .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1754                            .count();
1755                        let hanger_pos = if edges == 2 {
1756                            let pos = aabr.center().with_z(room.bounds.min.z);
1757                            painter.sprite(pos, SpriteKind::CoatrackMetalWoodland);
1758                            Some(pos)
1759                        } else {
1760                            None
1761                        };
1762
1763                        for dir in Dir2::iter()
1764                            .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1765                        {
1766                            let pos = dir
1767                                .select_aabr_with(*aabr, aabr.center())
1768                                .with_z(room.bounds.center().z + 1);
1769                            if hanger_pos.is_some_and(|p| p.xy() != pos.xy()) {
1770                                painter.rotated_sprite(
1771                                    pos,
1772                                    SpriteKind::WallLampSmall,
1773                                    dir.opposite().sprite_ori_legacy(),
1774                                );
1775                            }
1776                        }
1777                    }
1778                },
1779            }
1780            for detail in room.details.iter() {
1781                match *detail {
1782                    Detail::Bar { aabr } => {
1783                        for dir in Dir2::iter() {
1784                            let edge = dir.select_aabr(aabr);
1785                            let rot_dir = if field.chance(aabr.center().with_z(0), 0.5) {
1786                                dir.rotated_cw()
1787                            } else {
1788                                dir.rotated_ccw()
1789                            };
1790                            let rot_edge = rot_dir.select_aabr(aabr);
1791                            match (
1792                                edge == dir.select_aabr(room_aabr),
1793                                rot_edge == rot_dir.select_aabr(room_aabr),
1794                            ) {
1795                                (false, _) => {
1796                                    let (min, max) = (
1797                                        dir.select_aabr_with(
1798                                            aabr,
1799                                            Vec2::broadcast(rot_dir.select_aabr(aabr)),
1800                                        ),
1801                                        dir.select_aabr_with(
1802                                            aabr,
1803                                            Vec2::broadcast(rot_dir.opposite().select_aabr(aabr)),
1804                                        ),
1805                                    );
1806                                    painter
1807                                        .aabb(aabb(Aabb {
1808                                            min: (min - rot_dir.to_vec2())
1809                                                .with_z(room.bounds.min.z),
1810                                            max: max.with_z(room.bounds.min.z),
1811                                        }))
1812                                        .fill(wall_detail_fill.clone());
1813                                    painter
1814                                        .aabb(aabb(Aabb {
1815                                            min: min.with_z(room.bounds.min.z + 3),
1816                                            max: max.with_z(room.bounds.max.z),
1817                                        }))
1818                                        .fill(wall_detail_fill.clone());
1819                                },
1820                                (true, true) => {
1821                                    painter.sprite(
1822                                        dir.abs().vec2(edge, rot_edge).with_z(room.bounds.min.z),
1823                                        SpriteKind::CookingPot,
1824                                    );
1825                                },
1826                                (true, false) => {},
1827                            }
1828                        }
1829                    },
1830                    Detail::Stage { aabr } => {
1831                        painter
1832                            .aabb(aabb(Aabb {
1833                                min: aabr.min.with_z(room.bounds.min.z),
1834                                max: aabr.max.with_z(room.bounds.min.z),
1835                            }))
1836                            .fill(detail_fill.clone());
1837                        painter
1838                            .aabb(aabb(Aabb {
1839                                min: (aabr.min + 1).with_z(room.bounds.min.z),
1840                                max: (aabr.max - 1).with_z(room.bounds.min.z),
1841                            }))
1842                            .fill(wall_fill.clone());
1843                        for dir in Dir2::iter().filter(|dir| {
1844                            dir.select_aabr(aabr) != dir.select_aabr(room_aabr)
1845                                && dir.rotated_cw().select_aabr(aabr)
1846                                    != dir.rotated_cw().select_aabr(room_aabr)
1847                        }) {
1848                            let pos = dir.select_aabr_with(
1849                                aabr,
1850                                Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)),
1851                            );
1852                            painter
1853                                .column(pos, room.bounds.min.z..=room.bounds.max.z)
1854                                .fill(wall_detail_fill.clone());
1855
1856                            for dir in Dir2::iter() {
1857                                painter.rotated_sprite(
1858                                    pos.with_z(room.bounds.center().z + 1) + dir.to_vec2(),
1859                                    SpriteKind::WallSconce,
1860                                    dir.sprite_ori_legacy(),
1861                                );
1862                            }
1863                        }
1864                    },
1865                    Detail::Table { pos, chairs } => {
1866                        let pos = pos.with_z(room.bounds.min.z);
1867                        painter.sprite(pos, SpriteKind::DiningtableWoodWoodlandSquare);
1868                        for dir in chairs.into_iter() {
1869                            painter.rotated_sprite(
1870                                pos + dir.to_vec2(),
1871                                SpriteKind::ChairWoodWoodland2,
1872                                dir.opposite().sprite_ori(),
1873                            );
1874                        }
1875                    },
1876                }
1877            }
1878        }
1879
1880        // Fill in wall details
1881        for wall in self.walls.values() {
1882            let kinds = (wall.from.map(get_kind), wall.to.map(get_kind));
1883            let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = kinds {
1884                let in_dir = if to.is_none() {
1885                    -wall.to_dir
1886                } else {
1887                    wall.to_dir
1888                };
1889
1890                Some((in_dir, room))
1891            } else {
1892                None
1893            };
1894            if let Some((in_dir, room)) = in_dir_room {
1895                let width = in_dir.orthogonal().select(wall.end - wall.start).abs();
1896                let wall_center = (wall.start + wall.end) / 2;
1897                let door_dist = wall.door_bounds().map_or(i32::MAX, |door| {
1898                    (door.min - wall_center)
1899                        .map(|x| x.abs())
1900                        .reduce_max()
1901                        .max((door.max - wall_center).map(|x| x.abs()).reduce_max())
1902                });
1903                match room {
1904                    RoomKind::Garden => {
1905                        if door_dist >= 2 {
1906                            painter.rotated_sprite(
1907                                wall_center.with_z(wall.base_alt + 1),
1908                                SpriteKind::Planter,
1909                                in_dir.sprite_ori_legacy(),
1910                            );
1911                        }
1912                    },
1913                    _ => {
1914                        if width >= 5
1915                            && door_dist > 3
1916                            && wall.base_alt >= land.get_alt_approx(wall_center) as i32
1917                        {
1918                            painter
1919                                .aabb(aabb(Aabb {
1920                                    min: (wall_center + in_dir.rotated_ccw().to_vec2())
1921                                        .with_z(wall.base_alt + 1),
1922                                    max: (wall_center + in_dir.rotated_cw().to_vec2())
1923                                        .with_z(wall.base_alt + 2),
1924                                }))
1925                                .fill(Fill::sprite_ori(
1926                                    SpriteKind::Window1,
1927                                    in_dir.sprite_ori_legacy(),
1928                                ));
1929                        }
1930                    },
1931                }
1932            }
1933            if let Some(door) = wall.door_bounds()
1934                && !matches!(kinds, (Some(RoomKind::Garden), Some(RoomKind::Garden)))
1935            {
1936                let orth = wall.to_dir.orthogonal();
1937                painter
1938                    .aabb(aabb(Aabb {
1939                        min: (door.min - orth.to_vec2()).with_z(wall.base_alt),
1940                        max: (door.max + orth.to_vec2()).with_z(wall.base_alt + 3),
1941                    }))
1942                    .fill(detail_fill.clone());
1943                painter
1944                    .aabb(aabb(Aabb {
1945                        min: (door.min - orth.to_vec2()).with_z(wall.base_alt - 1),
1946                        max: (door.max + orth.to_vec2()).with_z(wall.base_alt - 1),
1947                    }))
1948                    .fill(floor_fill.clone());
1949                painter
1950                    .aabb(aabb(Aabb {
1951                        min: (door.min + wall.to_dir.to_vec2()).with_z(wall.base_alt),
1952                        max: (door.max - wall.to_dir.to_vec2()).with_z(wall.base_alt + 2),
1953                    }))
1954                    .clear();
1955                if let Some((min, max, room, to_dir)) = get_door_stair(wall, door) {
1956                    // Place a ramp if the door is lower than the room alt.
1957                    painter
1958                        .ramp(
1959                            aabb(Aabb {
1960                                min: (min - to_dir.to_vec2() * 3).with_z(wall.base_alt),
1961                                max: max.with_z(room.bounds.min.z + 2),
1962                            }),
1963                            to_dir,
1964                        )
1965                        // TOOD: For zoomy worldgen, this a sheared aabb.
1966                        .without(
1967                            painter
1968                                .ramp(
1969                                    aabb(Aabb {
1970                                        min: (min + to_dir.to_vec2() * 2).with_z(wall.base_alt),
1971                                        max: max.with_z(room.bounds.min.z - 1),
1972                                    }),
1973                                    to_dir,
1974                                )
1975                        )
1976                        .clear();
1977                }
1978                if let Some((in_dir, _room)) = in_dir_room {
1979                    let sprite = match in_dir.rotated_cw().select(door.size()) {
1980                        2.. => SpriteKind::DoorWide,
1981                        _ => SpriteKind::Door,
1982                    };
1983                    painter.rotated_sprite(
1984                        in_dir
1985                            .rotated_cw()
1986                            .select_aabr_with(door, door.min)
1987                            .with_z(wall.base_alt),
1988                        sprite,
1989                        in_dir.sprite_ori_legacy(),
1990                    );
1991                    painter.rotated_sprite(
1992                        in_dir
1993                            .rotated_ccw()
1994                            .select_aabr_with(door, door.min)
1995                            .with_z(wall.base_alt),
1996                        sprite,
1997                        in_dir.opposite().sprite_ori_legacy(),
1998                    );
1999
2000                    let dir = match field.chance(door.min.with_z(wall.base_alt), 0.5) {
2001                        true => in_dir.rotated_cw(),
2002                        false => in_dir.rotated_ccw(),
2003                    };
2004
2005                    let pos =
2006                        dir.select_aabr_with(door, door.min) + dir.to_vec2() - in_dir.to_vec2();
2007
2008                    painter.rotated_sprite_with_cfg(
2009                        pos.with_z(wall.base_alt + 2),
2010                        SpriteKind::HangingSign,
2011                        in_dir.opposite().sprite_ori_legacy(),
2012                        SpriteCfg {
2013                            content: Some(Content::Plain(self.name.clone())),
2014                            ..Default::default()
2015                        },
2016                    );
2017                }
2018            }
2019        }
2020
2021        // Fill stairs
2022        for roof in self.roofs.values() {
2023            if let Some((stairs_aabb, dir)) = roof.stairs {
2024                painter
2025                    .aabb(aabb(dir.to_dir3().trim_aabb(
2026                        Aabb {
2027                            min: stairs_aabb.min.with_z(roof.min_z),
2028                            max: stairs_aabb.max.with_z(roof.min_z),
2029                        },
2030                        (dir.to_dir3().select(stairs_aabb.size()) - 4).max(0),
2031                    )))
2032                    .clear();
2033                painter
2034                    .ramp(aabb(stairs_aabb), dir)
2035                    .fill(floor_fill.clone());
2036                painter
2037                    .ramp(
2038                        aabb(Dir3::NegZ.trim_aabb(dir.to_dir3().trim_aabb(stairs_aabb, 1), 1)),
2039                        dir,
2040                    )
2041                    .clear();
2042            }
2043        }
2044    }
2045}