use super::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HeightmapRead {
Read {
path: String,
width: u32,
depth: u32,
grey: Vec<u8>,
},
Unreadable {
path: String,
why: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ground {
kind: GroundKind,
region_min: [i64; 3],
region_max: [i64; 3],
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum GroundKind {
None,
Solid {
block: String,
},
Open {
surface: String,
below: String,
tops: Tops,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum Tops {
Flat(i64),
Map {
width: i64,
depth: i64,
tops: Vec<i64>,
},
Unresolved,
}
#[must_use]
pub fn pixel_top(base_y: i64, range: u32, value: u8) -> i64 {
base_y + (i64::from(value) * i64::from(range)) / 255
}
impl Ground {
#[must_use]
pub fn of(c: &Campaign) -> Ground {
let Some(plan) = c.site_plan.as_ref().map(|p| &p.content) else {
return Ground {
kind: GroundKind::None,
region_min: [0; 3],
region_max: [-1; 3],
};
};
let region_min = plan.region.min;
let region_max = plan.region.max();
let kind = match &plan.fill {
Fill::Solid { block } => GroundKind::Solid {
block: block.clone(),
},
Fill::Open {
terrain,
surface,
below,
} => GroundKind::Open {
surface: surface.clone(),
below: below.clone(),
tops: Self::tops(plan, terrain, c.heightmap.as_ref()),
},
};
Ground {
kind,
region_min,
region_max,
}
}
fn tops(plan: &SitePlanContent, terrain: &Terrain, read: Option<&HeightmapRead>) -> Tops {
match terrain {
Terrain::Flat { datum } => plan
.datums
.iter()
.find(|d| &d.id == datum)
.map_or(Tops::Unresolved, |d| Tops::Flat(d.y - 1)),
Terrain::Heightmap {
heightmap,
base_y,
range,
} => {
let Some(HeightmapRead::Read {
path,
width,
depth,
grey,
}) = read
else {
return Tops::Unresolved;
};
let (w, d) = (
i64::from(plan.region.extent[0].get()),
i64::from(plan.region.extent[2].get()),
);
if path != heightmap || i64::from(*width) != w || i64::from(*depth) != d {
return Tops::Unresolved;
}
Tops::Map {
width: w,
depth: d,
tops: grey
.iter()
.map(|v| pixel_top(*base_y, *range, *v))
.collect(),
}
}
}
}
#[must_use]
pub fn solid(block: &str) -> Ground {
Ground {
kind: GroundKind::Solid {
block: block.to_string(),
},
region_min: [i64::MIN / 4; 3],
region_max: [i64::MAX / 4; 3],
}
}
#[must_use]
pub fn open_flat(top: i64, surface: &str, below: &str) -> Ground {
Ground {
kind: GroundKind::Open {
surface: surface.to_string(),
below: below.to_string(),
tops: Tops::Flat(top),
},
region_min: [i64::MIN / 4; 3],
region_max: [i64::MAX / 4; 3],
}
}
#[must_use]
pub fn is_open(&self) -> bool {
matches!(self.kind, GroundKind::Open { .. })
}
#[must_use]
pub fn is_declared(&self) -> bool {
!matches!(self.kind, GroundKind::None)
}
#[must_use]
pub fn top(&self, x: i64, z: i64) -> Option<i64> {
let GroundKind::Open { tops, .. } = &self.kind else {
return None;
};
match tops {
Tops::Flat(y) => Some(*y),
Tops::Map { width, depth, tops } => {
let px = (x - self.region_min[0]).clamp(0, width - 1);
let pz = (z - self.region_min[2]).clamp(0, depth - 1);
tops.get(usize::try_from(pz * width + px).ok()?).copied()
}
Tops::Unresolved => None,
}
}
#[must_use]
pub fn fill_block(&self, cell: [i64; 3]) -> Option<&str> {
match &self.kind {
GroundKind::None => None,
GroundKind::Solid { block } => Some(block),
GroundKind::Open { surface, below, .. } => {
let top = self.top(cell[0], cell[2])?;
match cell[1].cmp(&top) {
std::cmp::Ordering::Greater => None,
std::cmp::Ordering::Equal => Some(surface),
std::cmp::Ordering::Less => Some(below),
}
}
}
}
#[must_use]
pub fn ground_block(&self, cell: [i64; 3], g: i64) -> &str {
match &self.kind {
GroundKind::None => "minecraft:air",
GroundKind::Solid { block } => block,
GroundKind::Open { surface, below, .. } => {
if cell[1] >= g {
surface
} else {
below
}
}
}
}
#[must_use]
pub fn volume_block(&self, role: VolumeRole, cell: [i64; 3], vol_top: i64) -> Option<&str> {
match (role, &self.kind) {
(VolumeRole::Clearance, _) | (_, GroundKind::None) => None,
(_, GroundKind::Solid { block }) => Some(block),
(VolumeRole::Massif, GroundKind::Open { below, .. }) => Some(below),
(VolumeRole::Ground, GroundKind::Open { surface, below, .. }) => {
Some(if cell[1] == vol_top { surface } else { below })
}
}
}
#[must_use]
pub fn region(&self) -> ([i64; 3], [i64; 3]) {
(self.region_min, self.region_max)
}
}