use super::*;
use crate::{util::NEIGHBORS, Land};
use rand::prelude::*;
use std::ops::{Add, Div, Mul};
use vek::*;
struct Cell {
alt: i32,
colonade: Option<i32>,
}
const CELL_SIZE: i32 = 16;
pub struct Citadel {
name: String,
_seed: u32,
origin: Vec3<i32>,
radius: i32,
grid: Grid<Option<Cell>>,
}
impl Citadel {
pub fn generate(wpos: Vec2<i32>, land: &Land, rng: &mut impl Rng) -> Self {
let alt = land.get_alt_approx(wpos) as i32;
let name = NameGen::location(rng).generate_town();
let seed = rng.gen();
let origin = wpos.with_z(alt);
let radius = 150;
let cell_radius = radius / CELL_SIZE;
let mut grid = Grid::populate_from(Vec2::broadcast((cell_radius + 1) * 2), |pos| {
let rpos = pos - cell_radius;
if rpos.magnitude_squared() < cell_radius.pow(2) {
let height = Lerp::lerp(
120.0,
24.0,
rpos.map(i32::abs).reduce_max() as f32 / cell_radius as f32,
);
let level_height = 32.0;
Some(Cell {
alt: land
.get_alt_approx(wpos + rpos * CELL_SIZE + CELL_SIZE / 2)
.add(height)
.div(level_height)
.floor()
.mul(level_height) as i32,
colonade: None,
})
} else {
None
}
});
for y in 0..grid.size().y {
for x in 0..grid.size().x {
let pos = Vec2::new(x, y);
if let Some(min_alt) = NEIGHBORS
.into_iter()
.filter_map(|rpos| Some(grid.get(pos + rpos)?.as_ref()?.alt))
.min()
{
let Some(Some(cell)) = grid.get_mut(pos) else {
continue;
};
if min_alt < cell.alt {
cell.colonade = Some(min_alt);
}
}
}
}
Self {
name,
_seed: seed,
origin,
radius,
grid,
}
}
pub fn name(&self) -> &str { &self.name }
pub fn radius(&self) -> i32 { self.radius }
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
trees: (wpos - self.origin).map(i32::abs).reduce_max() > self.radius,
waypoints: false,
..SpawnRules::default()
}
}
fn wpos_cell(&self, wpos: Vec2<i32>) -> Vec2<i32> {
(wpos - self.origin) / CELL_SIZE + self.grid.size() / 2
}
fn cell_wpos(&self, pos: Vec2<i32>) -> Vec2<i32> {
(pos - self.grid.size() / 2) * CELL_SIZE + self.origin
}
}
impl Structure for Citadel {
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"render_citadel\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "render_citadel")]
fn render_inner(&self, _site: &Site, land: &Land, painter: &Painter) {
for (pos, cell) in self.grid.iter_area(
self.wpos_cell(painter.render_aabr().min) - 1,
Vec2::<i32>::from(painter.render_aabr().size()) / CELL_SIZE + 2,
) {
if let Some(cell) = cell {
let wpos = self.cell_wpos(pos);
painter
.aabb(Aabb {
min: wpos.with_z(cell.alt),
max: (wpos + CELL_SIZE).with_z(cell.alt + 16),
})
.clear();
let mut prim = painter.aabb(Aabb {
min: wpos.with_z(land.get_alt_approx(wpos + CELL_SIZE / 2) as i32 - 32),
max: (wpos + CELL_SIZE).with_z(cell.alt),
});
if let Some(colonade_alt) = cell.colonade {
let hole = painter
.aabb(Aabb {
min: wpos.with_z(colonade_alt),
max: (wpos + CELL_SIZE).with_z(cell.alt),
})
.intersect(painter.prim(Primitive::Superquadric {
aabb: Aabb {
min: (wpos - 1).with_z(colonade_alt - 32),
max: (wpos + 1 + CELL_SIZE).with_z(cell.alt - 1),
},
degree: 2.5,
}));
hole.clear();
prim = prim.without(hole);
}
for dir in CARDINALS {
if self
.grid
.get(pos + dir)
.and_then(Option::as_ref)
.map_or(true, |near| near.alt < cell.alt)
{
let offset = wpos + CELL_SIZE / 2 + dir * CELL_SIZE / 2;
let rad = dir.map(|e| if e == 0 { CELL_SIZE / 2 + 1 } else { 1 });
let height = if pos.sum() % 2 == 0 { 5 } else { 2 };
prim = prim.union(painter.aabb(Aabb {
min: (offset - rad).with_z(cell.alt - 6),
max: (offset + rad).with_z(cell.alt + height),
}));
}
}
prim.fill(Fill::Brick(BlockKind::Rock, Rgb::new(100, 100, 100), 20));
}
}
}
}