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