veloren_world/site/plot/
citadel.rs1use 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 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 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 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}