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
75pub 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 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 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 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 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 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.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}