Skip to main content

veloren_world/site/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.random();
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    fn wpos_cell(&self, wpos: Vec2<i32>) -> Vec2<i32> {
88        (wpos - self.origin) / CELL_SIZE + self.grid.size() / 2
89    }
90
91    fn cell_wpos(&self, pos: Vec2<i32>) -> Vec2<i32> {
92        (pos - self.grid.size() / 2) * CELL_SIZE + self.origin
93    }
94}
95
96impl Structure for Citadel {
97    #[cfg(feature = "dyn-lib")]
98    #[unsafe(export_name = "as_dyn_structure_citadel")]
99    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
100        Some((Self::as_dyn_impl(self), "as_dyn_structure_citadel"))
101    }
102
103    fn spawn_rules_inner(
104        &self,
105        spawn_rules: &mut SpawnRules,
106        _land: &Land,
107        wpos: Vec2<i32>,
108        _weight: f32,
109    ) {
110        spawn_rules.trees &= (wpos - self.origin).map(i32::abs).reduce_max() > self.radius;
111        spawn_rules.waypoints = false;
112    }
113
114    fn render_inner(&self, _site: &Site, land: &Land, painter: &Painter) {
115        for (pos, cell) in self.grid.iter_area(
116            self.wpos_cell(painter.render_aabr().min) - 1,
117            Vec2::<i32>::from(painter.render_aabr().size()) / CELL_SIZE + 2,
118        ) {
119            if let Some(cell) = cell {
120                let wpos = self.cell_wpos(pos);
121                // Clear space above
122                painter
123                    .aabb(Aabb {
124                        min: wpos.with_z(cell.alt),
125                        max: (wpos + CELL_SIZE).with_z(cell.alt + 16),
126                    })
127                    .clear();
128
129                let mut prim = painter.aabb(Aabb {
130                    min: wpos.with_z(land.get_alt_approx(wpos + CELL_SIZE / 2) as i32 - 32),
131                    max: (wpos + CELL_SIZE).with_z(cell.alt),
132                });
133
134                // Colonades under cells
135                if let Some(colonade_alt) = cell.colonade {
136                    let hole = painter
137                        .aabb(Aabb {
138                            min: wpos.with_z(colonade_alt),
139                            max: (wpos + CELL_SIZE).with_z(cell.alt),
140                        })
141                        .intersect(painter.prim(Primitive::Superquadric {
142                            aabb: Aabb {
143                                min: (wpos - 1).with_z(colonade_alt - 32),
144                                max: (wpos + 1 + CELL_SIZE).with_z(cell.alt - 1),
145                            },
146                            degree: 2.5,
147                        }));
148                    hole.clear();
149                    prim = prim.without(hole);
150                }
151
152                // Walls around cells
153                for dir in CARDINALS {
154                    if self
155                        .grid
156                        .get(pos + dir)
157                        .and_then(Option::as_ref)
158                        .is_none_or(|near| near.alt < cell.alt)
159                    {
160                        let offset = wpos + CELL_SIZE / 2 + dir * CELL_SIZE / 2;
161                        let rad = dir.map(|e| if e == 0 { CELL_SIZE / 2 + 1 } else { 1 });
162                        let height = if pos.sum() % 2 == 0 { 5 } else { 2 };
163                        prim = prim.union(painter.aabb(Aabb {
164                            min: (offset - rad).with_z(cell.alt - 6),
165                            max: (offset + rad).with_z(cell.alt + height),
166                        }));
167                    }
168                }
169
170                prim.fill(Fill::Brick(BlockKind::Rock, Rgb::new(100, 100, 100), 20));
171            }
172        }
173    }
174}