use super::{Draw, Transitions};
use crate::{CHUNK_SIZE, Grid, Plane};
use std::collections::BTreeMap;
const SIDE: usize = CHUNK_SIZE as usize;
const CELLS: usize = SIDE * SIDE;
const WINDOW: usize = SIDE + 2;
pub trait TerrainSource {
fn chunk_ground(&self, layer: i32, cx: i64, cy: i64) -> Option<&[Grid]>;
}
impl TerrainSource for Plane {
fn chunk_ground(&self, layer: i32, cx: i64, cy: i64) -> Option<&[Grid]> {
self.chunks
.get(&(layer, cx, cy))
.map(|chunk| chunk.ground.as_slice())
}
}
const fn mix(value: u64) -> u64 {
let value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
let value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
value ^ (value >> 31)
}
#[must_use]
pub fn variant_hash(seed: u32, layer: i32, x: i64, y: i64) -> u64 {
let hash = mix(u64::from(seed));
let hash = mix(hash ^ i64::from(layer).cast_unsigned());
let hash = mix(hash ^ x.cast_unsigned());
mix(hash ^ y.cast_unsigned())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolvedChunk<'a> {
layers: Vec<Vec<Option<&'a str>>>,
}
impl<'a> ResolvedChunk<'a> {
#[must_use]
pub fn value(&self, ground: usize, index: usize) -> Option<&'a str> {
self.layers.get(ground)?.get(index).copied().flatten()
}
#[must_use]
pub fn at(&self, ground: usize, [x, y]: [u16; 2]) -> Option<&'a str> {
let (x, y) = (usize::from(x), usize::from(y));
if x < SIDE && y < SIDE {
self.value(ground, y * SIDE + x)
} else {
None
}
}
#[must_use]
pub fn ground_layers(&self) -> usize {
self.layers.len()
}
pub fn values(&self) -> impl Iterator<Item = &'a str> + '_ {
self.layers
.iter()
.flat_map(|tiles| tiles.iter().filter_map(|tile| *tile))
}
#[must_use]
pub fn grids(&self) -> Vec<Grid> {
self.layers
.iter()
.map(|tiles| {
let mut indexes: BTreeMap<&str, u16> = BTreeMap::new();
let mut palette = Vec::new();
let cells = tiles
.iter()
.map(|tile| {
tile.map_or(0, |value| {
*indexes.entry(value).or_insert_with(|| {
palette.push(value.to_owned());
u16::try_from(palette.len()).unwrap_or(u16::MAX)
})
})
})
.collect();
Grid { palette, cells }
})
.collect()
}
}
fn neighbour_bits(neighbours: &[Option<usize>; 8], test: impl Fn(Option<usize>) -> bool) -> u8 {
let mut bits = 0;
for (bit, neighbour) in neighbours.iter().enumerate() {
if test(*neighbour) {
bits |= 1 << bit;
}
}
bits
}
fn cell(grid: Option<&Grid>, index: usize) -> Option<&str> {
grid?.value(index)
}
impl Transitions {
#[must_use]
pub fn resolve_chunk<'a, S>(
&'a self,
source: &'a S,
layer: i32,
cx: i64,
cy: i64,
) -> ResolvedChunk<'a>
where
S: TerrainSource + ?Sized,
{
let own = source.chunk_ground(layer, cx, cy).unwrap_or(&[]);
let mut layers: Vec<Vec<Option<&'a str>>> = (0..self.ground_layers)
.map(|ground| {
(0..CELLS)
.map(|index| cell(own.get(ground), index))
.collect()
})
.collect();
let window = self.window(source, layer, cx, cy);
let terrain_at = |x: usize, y: usize| window.get(y * WINDOW + x).copied().flatten();
let mut drawn: Vec<Option<(usize, &'a str)>> = vec![None; self.ground_layers];
let side = i64::from(CHUNK_SIZE);
let (left, top) = (cx.wrapping_mul(side), cy.wrapping_mul(side));
for (y, plane_y) in (1..=SIDE).zip((0..).map(|offset: i64| top.wrapping_add(offset))) {
for (x, plane_x) in (1..=SIDE).zip((0..).map(|offset: i64| left.wrapping_add(offset))) {
let Some(terrain) = terrain_at(x, y) else {
continue;
};
let neighbours = [
terrain_at(x, y - 1),
terrain_at(x + 1, y - 1),
terrain_at(x + 1, y),
terrain_at(x + 1, y + 1),
terrain_at(x, y + 1),
terrain_at(x - 1, y + 1),
terrain_at(x - 1, y),
terrain_at(x - 1, y - 1),
];
let index = (y - 1) * SIDE + (x - 1);
let blocked = (0..self.ground_layers.min(8)).fold(0_u8, |blocked, output| {
if output == self.terrain_layer || cell(own.get(output), index).is_some() {
blocked | (1 << output)
} else {
blocked
}
});
self.draw_tile(
terrain,
&neighbours,
blocked,
(layer, plane_x, plane_y),
&mut drawn,
);
for (output, entry) in drawn.iter().enumerate() {
if let (Some((_, tile)), Some(slot)) = (
entry,
layers
.get_mut(output)
.and_then(|tiles| tiles.get_mut(index)),
) {
*slot = Some(*tile);
}
}
}
}
ResolvedChunk { layers }
}
fn draw_tile<'a>(
&'a self,
terrain: usize,
neighbours: &[Option<usize>; 8],
blocked: u8,
(layer, x, y): (i32, i64, i64),
drawn: &mut [Option<(usize, &'a str)>],
) {
drawn.fill(None);
let owners = std::iter::once(terrain).chain(neighbours.iter().enumerate().filter_map(
|(seen, neighbour)| {
let neighbour = (*neighbour)?;
(neighbour != terrain && !neighbours[..seen].contains(&Some(neighbour)))
.then_some(neighbour)
},
));
for owner in owners {
let Some(range) = self
.terrains
.get(owner)
.map(|owner| owner.transitions.clone())
else {
continue;
};
let same = neighbour_bits(neighbours, |neighbour| neighbour == Some(owner));
for position in range.rev() {
let Some(transition) = self.list.get(position) else {
continue;
};
let output = transition.layer;
if blocked & (1 << output.min(7)) != 0
|| drawn
.get(output)
.is_some_and(|entry| entry.is_some_and(|(at, _)| at > position))
{
continue;
}
let shows = if owner == terrain {
if transition.draw != Draw::Inside {
continue;
}
neighbour_bits(neighbours, |neighbour| {
neighbour.is_none_or(|neighbour| !transition.blends_with(neighbour))
})
} else if transition.draw == Draw::Outside && transition.blends_with(terrain) {
same
} else {
continue;
};
if !transition.draws(shows) {
continue;
}
let hash = variant_hash(transition.seed, layer, x, y);
if let (Some(tile), Some(slot)) =
(transition.draw_at(shows, hash), drawn.get_mut(output))
{
*slot = Some((position, tile));
}
}
}
}
fn window<S>(&self, source: &S, layer: i32, cx: i64, cy: i64) -> Vec<Option<usize>>
where
S: TerrainSource + ?Sized,
{
let mut window = vec![None; WINDOW * WINDOW];
for dy in -1_i64..=1 {
for dx in -1_i64..=1 {
let (Some(x), Some(y)) = (cx.checked_add(dx), cy.checked_add(dy)) else {
continue;
};
let Some(grid) = source
.chunk_ground(layer, x, y)
.and_then(|grids| grids.get(self.terrain_layer))
else {
continue;
};
let mut terrains: BTreeMap<u16, Option<usize>> = BTreeMap::new();
let span = |offset: i64| -> (usize, usize, usize) {
match offset {
-1 => (0, 1, SIDE - 1),
0 => (1, SIDE + 1, 0),
_ => (SIDE + 1, SIDE + 2, 0),
}
};
let (column_start, column_end, first_column) = span(dx);
let (row_start, row_end, first_row) = span(dy);
for (row, local_y) in (row_start..row_end).zip(first_row..) {
for (column, local_x) in (column_start..column_end).zip(first_column..) {
let Some(&value) = grid.cells.get(local_y * SIDE + local_x) else {
continue;
};
if value == 0 {
continue;
}
let terrain = *terrains.entry(value).or_insert_with(|| {
grid.palette
.get(usize::from(value) - 1)
.and_then(|proto| self.terrain_of(proto))
});
if let Some(slot) = window.get_mut(row * WINDOW + column) {
*slot = terrain;
}
}
}
}
}
window
}
}