veloren_world/site2/plot/
citadel.rs

1use super::*;
2use crate::{Land, util::NEIGHBORS};
3use rand::prelude::*;
4use std::ops::{Add, Div, Mul};
5use vek::*;
6
7struct Cell {
8    alt: i32,
9    colonade: Option<i32>,
10}
11
12const CELL_SIZE: i32 = 16;
13
14pub struct Citadel {
15    name: String,
16    _seed: u32,
17    origin: Vec3<i32>,
18    radius: i32,
19    grid: Grid<Option<Cell>>,
20}
21
22impl Citadel {
23    pub fn generate(wpos: Vec2<i32>, land: &Land, rng: &mut impl Rng) -> Self {
24        let alt = land.get_alt_approx(wpos) as i32;
25
26        let name = NameGen::location(rng).generate_town();
27        let seed = rng.gen();
28        let origin = wpos.with_z(alt);
29
30        let radius = 150;
31
32        let cell_radius = radius / CELL_SIZE;
33        let mut grid = Grid::populate_from(Vec2::broadcast((cell_radius + 1) * 2), |pos| {
34            let rpos = pos - cell_radius;
35            if rpos.magnitude_squared() < cell_radius.pow(2) {
36                let height = Lerp::lerp(
37                    120.0,
38                    24.0,
39                    rpos.map(i32::abs).reduce_max() as f32 / cell_radius as f32,
40                );
41                let level_height = 32.0;
42                Some(Cell {
43                    alt: land
44                        .get_alt_approx(wpos + rpos * CELL_SIZE + CELL_SIZE / 2)
45                        .add(height)
46                        .div(level_height)
47                        .floor()
48                        .mul(level_height) as i32,
49                    colonade: None,
50                })
51            } else {
52                None
53            }
54        });
55
56        for y in 0..grid.size().y {
57            for x in 0..grid.size().x {
58                let pos = Vec2::new(x, y);
59                if let Some(min_alt) = NEIGHBORS
60                    .into_iter()
61                    .filter_map(|rpos| Some(grid.get(pos + rpos)?.as_ref()?.alt))
62                    .min()
63                {
64                    let Some(Some(cell)) = grid.get_mut(pos) else {
65                        continue;
66                    };
67                    if min_alt < cell.alt {
68                        cell.colonade = Some(min_alt);
69                    }
70                }
71            }
72        }
73
74        Self {
75            name,
76            _seed: seed,
77            origin,
78            radius,
79            grid,
80        }
81    }
82
83    pub fn name(&self) -> &str { &self.name }
84
85    pub fn radius(&self) -> i32 { self.radius }
86
87    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
88        SpawnRules {
89            trees: (wpos - self.origin).map(i32::abs).reduce_max() > self.radius,
90            waypoints: false,
91            ..SpawnRules::default()
92        }
93    }
94
95    fn wpos_cell(&self, wpos: Vec2<i32>) -> Vec2<i32> {
96        (wpos - self.origin) / CELL_SIZE + self.grid.size() / 2
97    }
98
99    fn cell_wpos(&self, pos: Vec2<i32>) -> Vec2<i32> {
100        (pos - self.grid.size() / 2) * CELL_SIZE + self.origin
101    }
102}
103
104impl Structure for Citadel {
105    #[cfg(feature = "use-dyn-lib")]
106    const UPDATE_FN: &'static [u8] = b"render_citadel\0";
107
108    #[cfg_attr(feature = "be-dyn-lib", export_name = "render_citadel")]
109    fn render_inner(&self, _site: &Site, land: &Land, painter: &Painter) {
110        for (pos, cell) in self.grid.iter_area(
111            self.wpos_cell(painter.render_aabr().min) - 1,
112            Vec2::<i32>::from(painter.render_aabr().size()) / CELL_SIZE + 2,
113        ) {
114            if let Some(cell) = cell {
115                let wpos = self.cell_wpos(pos);
116                // Clear space above
117                painter
118                    .aabb(Aabb {
119                        min: wpos.with_z(cell.alt),
120                        max: (wpos + CELL_SIZE).with_z(cell.alt + 16),
121                    })
122                    .clear();
123
124                let mut prim = painter.aabb(Aabb {
125                    min: wpos.with_z(land.get_alt_approx(wpos + CELL_SIZE / 2) as i32 - 32),
126                    max: (wpos + CELL_SIZE).with_z(cell.alt),
127                });
128
129                // Colonades under cells
130                if let Some(colonade_alt) = cell.colonade {
131                    let hole = painter
132                        .aabb(Aabb {
133                            min: wpos.with_z(colonade_alt),
134                            max: (wpos + CELL_SIZE).with_z(cell.alt),
135                        })
136                        .intersect(painter.prim(Primitive::Superquadric {
137                            aabb: Aabb {
138                                min: (wpos - 1).with_z(colonade_alt - 32),
139                                max: (wpos + 1 + CELL_SIZE).with_z(cell.alt - 1),
140                            },
141                            degree: 2.5,
142                        }));
143                    hole.clear();
144                    prim = prim.without(hole);
145                }
146
147                // Walls around cells
148                for dir in CARDINALS {
149                    if self
150                        .grid
151                        .get(pos + dir)
152                        .and_then(Option::as_ref)
153                        .is_none_or(|near| near.alt < cell.alt)
154                    {
155                        let offset = wpos + CELL_SIZE / 2 + dir * CELL_SIZE / 2;
156                        let rad = dir.map(|e| if e == 0 { CELL_SIZE / 2 + 1 } else { 1 });
157                        let height = if pos.sum() % 2 == 0 { 5 } else { 2 };
158                        prim = prim.union(painter.aabb(Aabb {
159                            min: (offset - rad).with_z(cell.alt - 6),
160                            max: (offset + rad).with_z(cell.alt + height),
161                        }));
162                    }
163                }
164
165                prim.fill(Fill::Brick(BlockKind::Rock, Rgb::new(100, 100, 100), 20));
166            }
167        }
168    }
169}