Skip to main content

veloren_world/site/plot/
plaza.rs

1use super::*;
2use crate::{
3    ColumnSample, Land,
4    all::ForestKind,
5    site::{
6        generation::PrimitiveTransform,
7        util::sprites::{PainterSpriteExt, single_block},
8    },
9    util::{RandomField, Sampler},
10};
11use common::{
12    assets::AssetHandle,
13    terrain::{Block, BlockKind, StructuresGroup},
14};
15use enum_map::EnumMap;
16use itertools::Itertools;
17use lazy_static::lazy_static;
18use rand::{prelude::*, seq::IndexedRandom};
19use strum::IntoEnumIterator;
20use vek::*;
21
22#[derive(Clone, Copy)]
23enum TownCenterDecoration {
24    Gazebo {
25        roof: GazeboRoofKind,
26        roof_color: Rgb<u8>,
27    },
28    Tree,
29}
30
31#[derive(Clone, Copy)]
32enum GazeboRoofKind {
33    Gable,
34    Pyramid,
35}
36
37#[derive(Clone, Copy)]
38struct MarketStand {
39    aabr: Aabr<i32>,
40    facing: Dir2,
41    alt: i32,
42    roof_color: Rgb<u8>,
43    roof_pattern: Vec2<i32>,
44}
45
46#[derive(Clone)]
47enum PlazaKind {
48    Park {
49        surface_col: Rgb<u8>,
50        center_deco: TownCenterDecoration,
51    },
52    Stage {
53        aabr: Aabr<i32>,
54        facing: Dir2,
55        closed: bool,
56        roof_color: Rgb<u8>,
57    },
58    Market {
59        center: Aabr<i32>,
60        center_deco: Option<TownCenterDecoration>,
61        stands: Vec<MarketStand>,
62    },
63}
64
65#[derive(Default)]
66struct CornerMeta {
67    water_alt: i32,
68    alt: i32,
69}
70
71impl CornerMeta {
72    fn water(&self) -> bool { self.alt < self.water_alt }
73}
74
75/// Represents house data generated by the `generate()` method
76pub struct Plaza {
77    pub aabr: Aabr<i32>,
78    pub kind: RoadKind,
79    corner_meta: EnumMap<Dir2, CornerMeta>,
80    pub alt: i32,
81    pub hard_alt: Option<i32>,
82    dir: Dir2,
83    decoration: Option<PlazaKind>,
84    park_surface_col: Rgb<u8>,
85    wood_color: Rgb<u8>,
86}
87
88impl Plaza {
89    pub fn generate(
90        tile_aabr: Aabr<i32>,
91        kind: RoadKind,
92        site: &Site,
93        land: &Land,
94        index: IndexRef,
95        rng: &mut impl Rng,
96    ) -> Self {
97        let aabr = Aabr {
98            min: site.tile_wpos(tile_aabr.min),
99            max: site.tile_wpos(tile_aabr.max),
100        };
101        let mut iaabr = aabr;
102        iaabr.max -= 1;
103
104        let get_corner_meta = |wpos| {
105            land.column_sample(wpos, index)
106                .map(|col| CornerMeta {
107                    water_alt: col.water_level as i32,
108                    alt: col.alt as i32,
109                })
110                .unwrap_or_default()
111        };
112
113        let center = get_corner_meta(iaabr.center());
114
115        let corner_meta: EnumMap<Dir2, CornerMeta> = Dir2::iter()
116            .map(|d| {
117                let o = d.rotated_cw();
118                let pos = d.select_aabr_with(iaabr, o.select_aabr(iaabr));
119                (d, get_corner_meta(pos))
120            })
121            .collect();
122
123        let any_water = center.water() || corner_meta.values().any(|c| c.water());
124        let alt = land.get_alt_approx(aabr.center()) as i32;
125
126        let hard_alt = if any_water {
127            Some((land.get_alt_approx(aabr.center()) as i32).max(center.water_alt + 1))
128        } else {
129            None
130        };
131
132        let park_surface_col = if let Some(sample) = land.column_sample(aabr.center(), index) {
133            sample.surface_color
134        } else {
135            Rgb::new(0.5, 0.55, 0.0)
136        }
137        .map(|e| (e * 255.0) as u8);
138
139        let min_size = aabr.size().reduce_min();
140        // For now only generate plaza structures in woodland villages
141        let decoration = if land.get_gradient_approx(aabr.center()) < 1.5
142            && min_size >= TILE_SIZE as i32 * 5
143            && matches!(site.kind, Some(SiteKind::Refactor))
144        {
145            match rng.random_range(0..50) {
146                0..15 => {
147                    let stands_aabr = shrink_aabr(aabr, 4);
148                    let center_aabr = Aabr {
149                        min: stands_aabr.center() - 5,
150                        max: stands_aabr.center() + 5,
151                    };
152
153                    let roof_color = {
154                        let colors = [
155                            Rgb::new(21, 43, 48),
156                            Rgb::new(11, 23, 38),
157                            Rgb::new(45, 28, 21),
158                            Rgb::new(10, 55, 40),
159                            Rgb::new(5, 35, 15),
160                            Rgb::new(40, 5, 11),
161                            Rgb::new(55, 45, 11),
162                        ];
163                        *colors.choose(rng).unwrap_or(&Rgb::new(21, 43, 48))
164                    };
165
166                    let possible_center_deco = if stands_aabr.size().reduce_min() >= 26 {
167                        let outcomes = [
168                            TownCenterDecoration::Gazebo {
169                                roof: GazeboRoofKind::Gable,
170                                roof_color,
171                            },
172                            TownCenterDecoration::Gazebo {
173                                roof: GazeboRoofKind::Pyramid,
174                                roof_color,
175                            },
176                            TownCenterDecoration::Tree,
177                        ];
178                        outcomes.choose(rng).cloned()
179                    } else {
180                        None
181                    };
182
183                    let roof_colors = [
184                        Rgb::new(0x00, 0x28, 0x68),
185                        Rgb::new(0xCE, 0x11, 0x26),
186                        Rgb::new(0x68, 0xbf, 0xe5),
187                        Rgb::new(0xff, 0xd1, 0x00),
188                        Rgb::new(0x00, 0xa6, 0x51),
189                    ];
190
191                    let mut stand_aabrs = vec![];
192                    if possible_center_deco.is_some() {
193                        stand_aabrs.push(center_aabr);
194                    }
195                    let mut stands = Vec::new();
196
197                    for _ in 0..24 {
198                        if let Some(stand) = attempt(8, || {
199                            let offset = Vec2::new(
200                                rng.random_range(0..stands_aabr.size().w),
201                                rng.random_range(0..stands_aabr.size().h),
202                            );
203                            let corner = stands_aabr.min + offset;
204                            let width = rng.random_range(4..7);
205                            let size = 7;
206                            let facing_possibilities = Dir2::ALL
207                                .into_iter()
208                                .filter_map(|d| {
209                                    let score = if d.is_positive() {
210                                        d.select(stands_aabr.max - corner)
211                                    } else {
212                                        d.select(corner - stands_aabr.min)
213                                    }
214                                    .pow(2);
215                                    stands_aabr
216                                        .contains_point(corner + d.scale(width + 2))
217                                        .then_some((d, score))
218                                })
219                                .collect_vec();
220                            let facing = facing_possibilities
221                                .choose_weighted(rng, |(_, w)| *w)
222                                .map(|(d, _)| *d)
223                                .ok()?;
224                            let side = facing.choose_orthogonal(rng);
225
226                            let stand_aabr = Aabr {
227                                min: corner,
228                                max: corner + side.opposite().scale(size) + facing.scale(width),
229                            }
230                            .made_valid();
231
232                            let roof_pattern = if rng.random_bool(0.3) {
233                                Vec2::one() * rng.random_range(2..4)
234                            } else if rng.random_bool(0.5) {
235                                facing.scale(width * 10) + side.scale(rng.random_range(1..4))
236                            } else {
237                                side.scale(size * 10) + facing.scale(rng.random_range(1..4))
238                            };
239                            let is_even = aabr_corners(stand_aabr)
240                                .map(|p| land.get_alt_approx(p) as i32)
241                                .iter()
242                                .all_equal();
243                            (is_even
244                                && stands_aabr.contains_aabr(stand_aabr)
245                                && stand_aabrs.iter().all(|aabr| {
246                                    !shrink_aabr(*aabr, -2).intersection(stand_aabr).is_valid()
247                                }))
248                            .then_some(MarketStand {
249                                aabr: stand_aabr,
250                                facing,
251                                alt: hard_alt.unwrap_or_else(|| {
252                                    land.get_alt_approx(stand_aabr.center()) as i32
253                                }),
254                                roof_color: *roof_colors
255                                    .choose(rng)
256                                    .unwrap_or(&Rgb::new(0x00, 0x28, 0x68)),
257                                roof_pattern,
258                            })
259                        }) {
260                            stand_aabrs.push(stand.aabr);
261                            stands.push(stand);
262                        }
263                    }
264                    Some(PlazaKind::Market {
265                        center: center_aabr,
266                        center_deco: possible_center_deco,
267                        stands,
268                    })
269                },
270                15..20 => {
271                    let center = aabr.center();
272                    let aabr = Aabr {
273                        min: center - aabr.half_size() / 3,
274                        max: center + aabr.half_size() / 3,
275                    };
276                    let facing = Dir2::choose(rng);
277                    let facing_size = facing.select(aabr.size());
278                    let aabr = facing.opposite().trim_aabr(
279                        aabr,
280                        rng.random_range(facing_size / 12..(facing_size / 8) + 1),
281                    );
282                    let closed = rng.random_bool(0.5);
283                    let aabr = if closed {
284                        facing.opposite().translate_aabr(aabr, 4)
285                    } else {
286                        aabr
287                    };
288                    let roof_color = {
289                        let colors = [
290                            Rgb::new(21, 43, 48),
291                            Rgb::new(11, 23, 38),
292                            Rgb::new(45, 28, 21),
293                            Rgb::new(10, 55, 40),
294                            Rgb::new(5, 35, 15),
295                            Rgb::new(40, 5, 11),
296                            Rgb::new(55, 45, 11),
297                        ];
298                        *colors.choose(rng).unwrap_or(&Rgb::new(21, 43, 48))
299                    };
300                    Some(PlazaKind::Stage {
301                        aabr,
302                        facing,
303                        closed,
304                        roof_color,
305                    })
306                },
307                20..35 => {
308                    let roof_color = {
309                        let colors = [
310                            Rgb::new(21, 43, 48),
311                            Rgb::new(11, 23, 38),
312                            Rgb::new(45, 28, 21),
313                            Rgb::new(10, 55, 40),
314                            Rgb::new(5, 35, 15),
315                            Rgb::new(40, 5, 11),
316                            Rgb::new(55, 45, 11),
317                        ];
318                        *colors.choose(rng).unwrap_or(&Rgb::new(21, 43, 48))
319                    };
320                    let center_decos = [
321                        TownCenterDecoration::Tree,
322                        TownCenterDecoration::Gazebo {
323                            roof: GazeboRoofKind::Gable,
324                            roof_color,
325                        },
326                        TownCenterDecoration::Gazebo {
327                            roof: GazeboRoofKind::Pyramid,
328                            roof_color,
329                        },
330                    ];
331                    Some(PlazaKind::Park {
332                        surface_col: park_surface_col,
333                        center_deco: *center_decos.choose(rng).expect("center_decos is not empty"),
334                    })
335                },
336                _ => None,
337            }
338        } else {
339            None
340        };
341        let wood_color = match land
342            .make_forest_lottery(aabr.center())
343            .choose_seeded(rng.random())
344        {
345            Some(
346                ForestKind::Cedar
347                | ForestKind::AutumnTree
348                | ForestKind::Frostpine
349                | ForestKind::Mangrove,
350            ) => Rgb::new(63, 28, 12),
351            Some(ForestKind::Oak | ForestKind::Swamp | ForestKind::Baobab) => Rgb::new(102, 87, 63),
352            Some(ForestKind::Acacia | ForestKind::Birch | ForestKind::Palm) => {
353                Rgb::new(130, 104, 102)
354            },
355            Some(
356                ForestKind::Mapletree | ForestKind::Redwood | ForestKind::Pine | ForestKind::Cherry,
357            ) => Rgb::new(117, 95, 46),
358            _ => Rgb::new(63, 28, 12),
359        };
360        Self {
361            aabr,
362            kind,
363            corner_meta,
364            alt,
365            hard_alt,
366            dir: *RandomField::new(51)
367                .choose(aabr.center().with_z(center.alt), &Dir2::ALL)
368                .expect("Dir::ALL has len 4"),
369            decoration,
370            park_surface_col,
371            wood_color,
372        }
373    }
374}
375
376impl Structure for Plaza {
377    #[cfg(feature = "dyn-lib")]
378    #[unsafe(export_name = "as_dyn_structure_plaza")]
379    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
380        Some((Self::as_dyn_impl(self), "as_dyn_structure_plaza"))
381    }
382
383    fn spawn_rules_inner(
384        &self,
385        spawn_rules: &mut SpawnRules,
386        _land: &Land,
387        _wpos: Vec2<i32>,
388        weight: f32,
389    ) {
390        spawn_rules.prefer_alt(self.alt as f32, weight + weight.powi(1) * 45.0);
391    }
392
393    fn render_ordering(&self) -> u32 { 2 }
394
395    fn render_inner(&self, site: &Site, land: &Land, painter: &Painter) {
396        if let Some(alt) = self.hard_alt {
397            let wood_corner = Fill::Brick(BlockKind::Wood, Rgb::new(86, 50, 50), 10);
398            let bounds = Aabb {
399                min: self.aabr.min.with_z(alt),
400                max: self.aabr.max.with_z(alt + 1),
401            };
402            painter.aabb(bounds).fill(wood_corner.clone());
403            let mut iaabr = self.aabr;
404            let hsize = iaabr.half_size();
405            iaabr.min += 1;
406            iaabr.max -= 2;
407            for (d, meta) in self.corner_meta.iter() {
408                let o = d.rotated_cw();
409
410                let corner = d.select_aabr_with(iaabr, o.select_aabr(iaabr));
411
412                painter
413                    .column(corner, meta.alt - 5..alt)
414                    .fill(wood_corner.clone());
415
416                for (dir, index) in [(d, d.rotated_ccw()), (o, o)] {
417                    let dp = -dir;
418                    let min_alt = meta.alt.min(self.corner_meta[d.relative_to(index)].alt);
419                    for i in (0..dp.select(hsize)).step_by(8) {
420                        let p = corner + dp.to_vec2() * i;
421                        painter
422                            .column(p, min_alt - 5..alt)
423                            .fill(wood_corner.clone());
424                    }
425                }
426            }
427        }
428
429        let tile_aabr = Aabr {
430            min: site.wpos_tile_pos(self.aabr.min),
431            max: site.wpos_tile_pos(self.aabr.max) - 1,
432        };
433
434        for dir in Dir2::iter() {
435            let orth = dir.orthogonal();
436
437            for i in (orth.select(tile_aabr.min) + 1)..orth.select(tile_aabr.max) {
438                let tpos = dir.select_aabr_with(tile_aabr, i);
439                if (tpos.x + tpos.y) % 3 != 0 {
440                    continue;
441                }
442
443                if site.tiles.get(tpos + dir.to_vec2()).is_empty() {
444                    let wpos = site.tile_center_wpos(tpos);
445
446                    // TODO: Not sure if this is always correct
447                    let alt = self.hard_alt.unwrap_or(self.alt) + 1;
448                    let wpos = wpos.with_z(alt);
449                    self.kind.place_light(wpos, -dir, painter);
450                }
451            }
452        }
453
454        let rng = &mut rand::rng();
455        if rng.random_bool(0.5) {
456            let spec = [
457                "common.entity.wild.peaceful.cat",
458                "common.entity.wild.peaceful.dog",
459            ]
460            .choose(rng)
461            .unwrap();
462            let wpos_2d = Vec2::new(
463                rng.random_range(self.aabr.min.x..self.aabr.max.x),
464                rng.random_range(self.aabr.min.y..self.aabr.max.y),
465            );
466            painter.spawn(
467                EntityInfo::at(wpos_2d.with_z(self.hard_alt.unwrap_or(self.alt)).as_())
468                    .with_asset_expect(spec, rng, None)
469                    .with_alignment(Alignment::Tame),
470            );
471        }
472
473        if let Some(decoration) = self.decoration.clone() {
474            let wood = Fill::PlankWall(BlockKind::Wood, self.wood_color, 4);
475            let dark_wood = Fill::PlankWall(BlockKind::Wood, self.wood_color / 2, 4);
476            let darker_wood = Fill::PlankWall(BlockKind::Wood, self.wood_color / 4, 4);
477            let stone = Fill::Brick(BlockKind::Rock, Rgb::new(70, 65, 80), 24);
478            match decoration {
479                PlazaKind::Park {
480                    surface_col,
481                    center_deco,
482                } => {
483                    let aabr = shrink_aabr(self.aabr, 6);
484                    let center = aabr.center();
485                    let alt = land.get_alt_approx(center) as i32;
486
487                    painter
488                        .aabb(aabr_with_z(aabr, alt..alt + 2))
489                        .fill(stone.clone());
490
491                    painter
492                        .aabb(aabr_with_z(shrink_aabr(aabr, -1), alt..alt + 2))
493                        .fill(stone.clone());
494                    let iaabr = Aabr {
495                        min: aabr.min - 1,
496                        max: aabr.max,
497                    };
498                    painter.benches_around(
499                        SpriteKind::BenchWoodWoodland,
500                        8,
501                        4,
502                        shrink_aabr(iaabr, 2),
503                        alt + 2,
504                    );
505
506                    match center_deco {
507                        TownCenterDecoration::Gazebo { roof, roof_color } => {
508                            paint_gazebo(
509                                painter,
510                                shrink_aabr(aabr, 3),
511                                alt,
512                                4,
513                                wood.clone(),
514                                dark_wood.clone(),
515                                roof,
516                                roof_color,
517                            );
518                        },
519                        TownCenterDecoration::Tree => {
520                            paint_tree(painter, center.with_z(alt));
521                        },
522                    }
523                    painter
524                        .aabb(aabr_with_z(shrink_aabr(aabr, 2), alt..alt + 2))
525                        .fill(Fill::Block(Block::new(BlockKind::Grass, surface_col)));
526                },
527                PlazaKind::Stage {
528                    aabr,
529                    facing,
530                    closed,
531                    roof_color,
532                } => {
533                    let roof_fill = Fill::Brick(BlockKind::Wood, roof_color, 8);
534                    let height = 5;
535                    let alt = land.get_alt_approx(aabr.center()) as i32;
536                    let stage_alt = alt + 2;
537                    let side = facing.orthogonal();
538                    let main_aabr = if closed {
539                        facing.opposite().trim_aabr(aabr, 3)
540                    } else {
541                        aabr
542                    };
543                    let ground_aabb = painter.aabb(aabr_with_z(aabr, alt + 1..stage_alt));
544                    painter
545                        .aabb(aabr_with_z(main_aabr, alt - 1..stage_alt))
546                        .fill(dark_wood.clone());
547                    for corner in aabr_corners(main_aabr) {
548                        column(
549                            painter,
550                            corner.with_z(stage_alt + 0),
551                            height,
552                            darker_wood.clone(),
553                        );
554                    }
555                    for corner in aabr_corners(shrink_aabr(main_aabr, -1)) {
556                        single_block(
557                            painter,
558                            corner.with_z(stage_alt + height - 1),
559                            Block::air(SpriteKind::Lantern),
560                        );
561                    }
562
563                    if closed {
564                        let back_corner1 = facing
565                            .opposite()
566                            .select_aabr_with(main_aabr, side.select_aabr(main_aabr));
567                        let back_corner2 = facing
568                            .opposite()
569                            .select_aabr_with(main_aabr, side.opposite().select_aabr(main_aabr));
570                        painter
571                            .aabb(
572                                Aabb {
573                                    min: back_corner1.with_z(stage_alt),
574                                    max: (back_corner2 + facing.scale(1))
575                                        .with_z(stage_alt + height),
576                                }
577                                .made_valid(),
578                            )
579                            .fill(darker_wood.clone());
580                    }
581
582                    if closed {
583                        painter
584                            .cylinder(aabr_with_z(aabr, alt + 1..stage_alt))
585                            .intersect(ground_aabb)
586                            .fill(dark_wood.clone());
587                    }
588
589                    // Roof
590                    let aabr_one = shrink_aabr(main_aabr, -1);
591                    painter
592                        .aabb(aabr_with_z(
593                            aabr_one,
594                            stage_alt + height..stage_alt + height + 1,
595                        ))
596                        .fill(roof_fill.clone());
597                    let gable_aabr =
598                        side.trim_aabr(aabr_one, side.select(main_aabr.half_size() - 2));
599                    let gable_aabb =
600                        aabr_with_z(gable_aabr, stage_alt + height + 1..stage_alt + height + 4)
601                            .made_valid();
602
603                    let gable = painter.gable(gable_aabb, 3, facing);
604                    let gable_clear = painter.gable(
605                        aabr_with_z(
606                            Aabr {
607                                min: gable_aabr.min + side.scale(1),
608                                max: gable_aabr.min + side.scale(gable_aabr.size() - 1)
609                                    - facing.scale(1),
610                            },
611                            stage_alt + height..stage_alt + height + 3,
612                        ),
613                        2,
614                        facing,
615                    );
616                    gable.fill(roof_fill.clone());
617                    gable_clear.clear();
618                    gable_clear
619                        .translate(facing.opposite().scale(main_aabr.size() + 1).with_z(0))
620                        .clear();
621                },
622                PlazaKind::Market {
623                    center,
624                    center_deco,
625                    stands,
626                } => {
627                    let center_alt = land.get_alt_approx(center.center()) as i32;
628                    if let Some(center_deco) = center_deco {
629                        match center_deco {
630                            TownCenterDecoration::Gazebo { roof, roof_color } => {
631                                paint_gazebo(
632                                    painter,
633                                    center,
634                                    center_alt,
635                                    4,
636                                    wood.clone(),
637                                    dark_wood.clone(),
638                                    roof,
639                                    roof_color,
640                                );
641                            },
642                            TownCenterDecoration::Tree => {
643                                let alt = center_alt;
644                                painter
645                                    .aabb(aabr_with_z(center, alt..alt + 2))
646                                    .fill(stone.clone());
647                                painter
648                                    .aabb(aabr_with_z(shrink_aabr(center, 1), alt..alt + 2))
649                                    .fill(Fill::Block(Block::new(
650                                        BlockKind::Grass,
651                                        self.park_surface_col,
652                                    )));
653                                paint_tree(painter, center.center().with_z(center_alt + 9));
654                            },
655                        }
656                    }
657
658                    for stand in stands {
659                        let aabr = stand.aabr;
660                        let alt = stand.alt;
661                        let facing = stand.facing;
662                        let stand_height = 4;
663
664                        let roof = Fill::Checker(
665                            BlockKind::Wood,
666                            stand.roof_color,
667                            Rgb::white(),
668                            stand.roof_pattern,
669                        );
670
671                        let counter_fill = Fill::Sampling(std::sync::Arc::new(|center| {
672                            match RandomField::new(4973).get(center) % 128 {
673                                0..12 => Some(SpriteKind::VialEmpty),
674                                12..13 => Some(SpriteKind::PotionMinor),
675                                13..24 => Some(SpriteKind::Bowl),
676                                24..30 => Some(SpriteKind::Apple),
677                                30..32 => Some(SpriteKind::VeloriteFrag),
678                                _ => None,
679                            }
680                            .map(Block::air)
681                        }));
682                        let counter_dir = stand.facing.rotated_ccw();
683                        let corner = counter_dir
684                            .opposite()
685                            .select_aabr_with(aabr, counter_dir.rotated_cw().select_aabr(aabr));
686
687                        let counter = painter.aabb(
688                            Aabb {
689                                min: corner.with_z(alt + 1),
690                                max: (corner
691                                    + counter_dir.to_vec2() * aabr.size()
692                                    + counter_dir.rotated_ccw().to_vec2())
693                                .with_z(alt + 2),
694                            }
695                            .made_valid(),
696                        );
697                        counter.fill(Fill::sprite_ori(
698                            SpriteKind::CounterWoodMiddle,
699                            counter_dir.sprite_ori(),
700                        ));
701                        let stock = painter.aabb(
702                            Aabb {
703                                min: corner.with_z(alt + 2),
704                                max: (corner
705                                    + counter_dir.scale(aabr.size())
706                                    + counter_dir.rotated_ccw().scale(1))
707                                .with_z(alt + 3),
708                            }
709                            .made_valid(),
710                        );
711                        stock.fill(counter_fill.clone());
712
713                        {
714                            let mut corner = aabr.min.with_z(alt + 2);
715                            let size = aabr.size();
716                            for d in Dir2::ALL {
717                                column(
718                                    painter,
719                                    corner,
720                                    stand_height - 2,
721                                    Fill::Block(Block::air(SpriteKind::FencePost)),
722                                );
723
724                                column(
725                                    painter,
726                                    corner - Vec3::unit_z(),
727                                    1,
728                                    Fill::Block(Block::new(BlockKind::Wood, Rgb::new(115, 69, 44))),
729                                );
730                                corner += d.scale(size - 1);
731                            }
732                        }
733
734                        // Corner
735                        let c_back_1 = facing
736                            .opposite()
737                            .select_aabr_with(aabr, facing.rotated_cw().select_aabr(aabr));
738                        let c_back_2 = facing
739                            .opposite()
740                            .select_aabr_with(aabr, facing.rotated_ccw().select_aabr(aabr));
741                        let c_front_1 =
742                            facing.select_aabr_with(aabr, facing.rotated_cw().select_aabr(aabr));
743                        let c_front_2 =
744                            facing.select_aabr_with(aabr, facing.rotated_ccw().select_aabr(aabr));
745
746                        // Backwall
747                        painter
748                            .aabb(
749                                Aabb {
750                                    min: c_back_1.with_z(alt + 1),
751                                    max: (c_back_2 - facing.opposite().scale(1))
752                                        .with_z(alt + stand_height + 1),
753                                }
754                                .made_valid(),
755                            )
756                            .fill(Fill::Block(Block::new(
757                                BlockKind::Wood,
758                                Rgb::new(115, 69, 44),
759                            )));
760
761                        let make_strip = |side: i32, forward: i32, z_off: i32| {
762                            let c_front_1 = c_front_1 + facing.rotated_cw().scale(1);
763                            let c_front_2 = c_front_2 + facing.rotated_ccw().scale(1);
764                            painter
765                                .aabb(
766                                    Aabb {
767                                        min: (c_front_1 + facing.opposite().scale(forward))
768                                            .with_z(alt + stand_height + z_off),
769                                        max: (c_front_1
770                                            + facing.rotated_ccw().scale(side)
771                                            + facing.opposite().scale(forward + 1))
772                                        .with_z(alt + stand_height + z_off + 1),
773                                    }
774                                    .made_valid(),
775                                )
776                                .fill(roof.clone());
777                            painter
778                                .aabb(
779                                    Aabb {
780                                        min: (c_front_2 + facing.opposite().scale(forward))
781                                            .with_z(alt + stand_height + z_off),
782                                        max: (c_front_2
783                                            + facing.rotated_cw().scale(side)
784                                            + facing.opposite().scale(forward + 1))
785                                        .with_z(alt + stand_height + z_off + 1),
786                                    }
787                                    .made_valid(),
788                                )
789                                .fill(roof.clone());
790                            painter
791                                .aabb(
792                                    Aabb {
793                                        min: (c_front_1
794                                            + facing.opposite().scale(forward)
795                                            + facing.rotated_ccw().scale(side))
796                                        .with_z(alt + stand_height + z_off + 1),
797                                        max: (c_front_2
798                                            + facing.rotated_cw().scale(side)
799                                            + facing.opposite().scale(forward + 1))
800                                        .with_z(alt + stand_height + z_off + 2),
801                                    }
802                                    .made_valid(),
803                                )
804                                .fill(roof.clone());
805                        };
806
807                        let aabr_one = shrink_aabr(aabr, -1);
808                        let width = facing.select(aabr_one.size());
809
810                        make_strip(3, -1, 0);
811                        make_strip(2, 0, 0);
812                        make_strip(2, 1, 1);
813                        if width == 6 {
814                            make_strip(2, 2, 1);
815                            make_strip(2, 3, 0);
816                            make_strip(3, 4, 0);
817                        }
818                        if width == 7 {
819                            make_strip(1, 2, 1);
820                            make_strip(2, 3, 1);
821                            make_strip(2, 4, 0);
822                            make_strip(3, 5, 0);
823                        }
824                        if width == 8 {
825                            make_strip(1, 2, 1);
826                            make_strip(1, 3, 1);
827                            make_strip(2, 4, 1);
828                            make_strip(2, 5, 0);
829                            make_strip(3, 6, 0);
830                        }
831                    }
832                },
833            }
834        }
835    }
836
837    fn rel_terrain_offset(&self, col: &ColumnSample) -> i32 { col.riverless_alt as i32 }
838
839    fn terrain_surface_at_inner(
840        &self,
841        wpos: Vec2<i32>,
842        old: Block,
843        _rng: &mut ChaCha8Rng,
844        col: &ColumnSample,
845        z_off: i32,
846        _site: &Site,
847    ) -> Option<Block> {
848        let z = self.rel_terrain_offset(col) + z_off;
849        if col.water_level > col.alt || self.hard_alt.is_some_and(|alt| z < alt) {
850            return None;
851        };
852        if z_off <= -1 {
853            let block = self.kind.block(col, wpos.with_z(z), self.dir);
854            if old.is_filled() {
855                if old.is_terrain() { Some(block) } else { None }
856            } else if self.hard_alt.is_none() {
857                Some(block)
858            } else {
859                None
860            }
861        } else if old.is_fluid() || old.kind() == BlockKind::Snow || old.is_terrain() {
862            Some(old.into_vacant())
863        } else {
864            None
865        }
866    }
867}
868
869fn shrink_aabr(aabr: Aabr<i32>, shrink: impl Into<Vec2<i32>>) -> Aabr<i32> {
870    let shrink = shrink.into();
871    Aabr {
872        min: aabr.min + shrink,
873        max: aabr.max - shrink,
874    }
875}
876
877fn column(painter: &Painter, pos: Vec3<i32>, height: i32, fill: Fill) {
878    painter
879        .aabb(Aabb {
880            min: pos,
881            max: pos + Vec2::one() + Vec3::unit_z() * height,
882        })
883        .fill(fill.clone());
884}
885
886fn aabr_corners(aabr: Aabr<i32>) -> [Vec2<i32>; 4] {
887    let size = aabr.size();
888    core::array::from_fn(|i| {
889        let mut corner = aabr.min;
890        for t in 0..i {
891            let dir = Dir2::ALL[t];
892            corner += dir.scale(size - 1);
893        }
894        corner
895    })
896}
897
898fn paint_gazebo(
899    painter: &Painter,
900    aabr: Aabr<i32>,
901    alt: i32,
902    height: i32,
903    wood: Fill,
904    dark_wood: Fill,
905    roof: GazeboRoofKind,
906    roof_color: Rgb<u8>,
907) {
908    painter
909        .aabb(aabr_with_z(aabr, alt..alt + 2))
910        .fill(wood.clone());
911    let aabr = shrink_aabr(aabr, 1);
912    let size = aabr.size();
913    let roof_fill = Fill::Brick(BlockKind::Wood, roof_color, 8);
914    match roof {
915        GazeboRoofKind::Gable => {
916            let gable_height = 3;
917            let gable_inset = 5;
918            let gable_dir = Dir2::from_vec2(size);
919            let gable_aabb = aabr_with_z(
920                shrink_aabr(aabr, -1),
921                alt + height + 3..alt + height + 4 + gable_height,
922            );
923            let gable_aabb_prim = painter.aabb(gable_aabb);
924            let gable = painter.gable(gable_aabb, gable_inset, gable_dir);
925            gable.fill(roof_fill.clone());
926
927            let gable_clear = painter
928                .gable(
929                    aabr_with_z(
930                        Aabr {
931                            min: aabr.min,
932                            max: aabr.min + gable_dir.rotated_cw().scale(size) - gable_dir.scale(2),
933                        },
934                        alt + height + 2..alt + height + 3 + gable_height,
935                    ),
936                    gable_inset,
937                    gable_dir,
938                )
939                .intersect(gable_aabb_prim);
940            // Gable lower clear
941            gable.translate(Vec3::unit_z() * -2).clear();
942            gable_clear.clear();
943            gable_clear
944                .translate(gable_dir.scale(size + 1).with_z(0))
945                .clear();
946        },
947        GazeboRoofKind::Pyramid => {
948            let pyramid_height = 3;
949            let pyramid_aabb = aabr_with_z(
950                shrink_aabr(aabr, -1),
951                alt + height + 3..alt + height + 4 + pyramid_height,
952            );
953            painter.pyramid(pyramid_aabb).fill(roof_fill.clone());
954            painter
955                .aabb(aabr_with_z(
956                    shrink_aabr(aabr, 2),
957                    alt + height + 3..alt + height + 4,
958                ))
959                .clear();
960            painter
961                .aabb(aabr_with_z(
962                    shrink_aabr(aabr, 3),
963                    alt + height + 4..alt + height + 5,
964                ))
965                .clear();
966        },
967    }
968    for corner in aabr_corners(aabr) {
969        column(
970            painter,
971            corner.with_z(alt + 2),
972            height + 1,
973            dark_wood.clone(),
974        );
975    }
976    for corner in aabr_corners(shrink_aabr(aabr, -1)) {
977        single_block(
978            painter,
979            corner.with_z(alt + height + 2),
980            Block::air(SpriteKind::Lantern),
981        );
982    }
983}
984
985fn paint_tree(painter: &Painter, pos: Vec3<i32>) {
986    lazy_static! {
987        static ref FRUIT_TREES: AssetHandle<StructuresGroup> =
988            common::terrain::Structure::load_group("trees.fruit_trees");
989    }
990    let rng = RandomField::new(13579).get(pos) % 10;
991    let fruits = FRUIT_TREES.read();
992    let fruit = fruits[rng as usize % fruits.len()].clone();
993    painter
994        .prim(Primitive::Prefab(Box::new(fruit.clone())))
995        .translate(pos)
996        .fill(Fill::Prefab(Box::new(fruit), pos, rng));
997}