use alloc::vec::Vec;
use crate::real::Real;
use super::{ChunkId, ChunkLayout};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct StreamConfig<R: Real> {
load: R,
unload: R,
}
impl<R: Real> StreamConfig<R> {
pub fn new(load: R, unload: R) -> crate::Result<Self> {
let bad = |value: R| !value.is_finite() || value <= R::ZERO;
if bad(load) || bad(unload) || unload <= load {
return Err(crate::Error::InvalidCellSize {
value: f64::from(unload.as_f32()),
});
}
Ok(Self { load, unload })
}
#[must_use]
pub fn load(&self) -> R {
self.load
}
#[must_use]
pub fn unload(&self) -> R {
self.unload
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct StreamUpdate {
pub loaded: Vec<ChunkId>,
pub unloaded: Vec<ChunkId>,
}
impl StreamUpdate {
#[must_use]
pub const fn new() -> Self {
Self {
loaded: Vec::new(),
unloaded: Vec::new(),
}
}
pub fn reset(&mut self) {
self.loaded.clear();
self.unloaded.clear();
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.loaded.is_empty() && self.unloaded.is_empty()
}
}
#[derive(Clone, Debug, Default)]
pub struct ChunkStream {
resident: Vec<ChunkId>,
}
impl ChunkStream {
#[must_use]
pub const fn new() -> Self {
Self {
resident: Vec::new(),
}
}
#[must_use]
pub fn resident(&self) -> &[ChunkId] {
&self.resident
}
#[must_use]
pub fn len(&self) -> usize {
self.resident.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.resident.is_empty()
}
pub fn clear(&mut self, out: &mut StreamUpdate) {
out.reset();
out.unloaded.append(&mut self.resident);
}
pub fn update<R: Real>(
&mut self,
layout: &ChunkLayout<R>,
camera: [R; 3],
config: &StreamConfig<R>,
out: &mut StreamUpdate,
) -> crate::Result<()> {
out.reset();
let span = self.candidate_span(layout, camera, config.unload)?;
let mut next: Vec<ChunkId> = Vec::new();
for x in span.lo[0]..=span.hi[0] {
for y in span.lo[1]..=span.hi[1] {
for z in span.lo[2]..=span.hi[2] {
let id = ChunkId::new([x, y, z]);
let distance = distance_to_chunk(layout, id, camera);
let was = self.resident.binary_search(&id).is_ok();
let keep = if was {
distance <= config.unload
} else {
distance <= config.load
};
if keep {
next.push(id);
}
}
}
}
debug_assert!(next.windows(2).all(|w| w[0] < w[1]));
let (mut a, mut b) = (0usize, 0usize);
while a < self.resident.len() || b < next.len() {
match (self.resident.get(a), next.get(b)) {
(Some(old), Some(new)) if old == new => {
a += 1;
b += 1;
}
(Some(old), Some(new)) if old < new => {
out.unloaded.push(*old);
a += 1;
}
(Some(_), Some(new)) => {
out.loaded.push(*new);
b += 1;
}
(Some(old), None) => {
out.unloaded.push(*old);
a += 1;
}
(None, Some(new)) => {
out.loaded.push(*new);
b += 1;
}
(None, None) => break,
}
}
self.resident = next;
Ok(())
}
fn candidate_span<R: Real>(
&self,
layout: &ChunkLayout<R>,
camera: [R; 3],
radius: R,
) -> crate::Result<Span> {
let extent = R::from_f64(f64::from(layout.cells())) * layout.cell_size();
let here = layout.chunk_of(camera);
let reach = (radius / extent).floor();
let reach = if reach.is_finite() && reach >= R::ZERO {
i64::from(reach.as_f32() as i32) + 1
} else {
return Err(crate::Error::IndexSpaceExhausted { needed: u64::MAX });
};
let side = 2 * reach + 1;
let total = side.saturating_mul(side).saturating_mul(side);
if total > i64::from(u32::MAX) {
return Err(crate::Error::IndexSpaceExhausted {
needed: total as u64,
});
}
let mut span = Span {
lo: [0; 3],
hi: [0; 3],
};
for axis in 0..3 {
let centre = i64::from(here.coords[axis]);
span.lo[axis] = (centre - reach).clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32;
span.hi[axis] = (centre + reach).clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32;
}
Ok(span)
}
}
struct Span {
lo: [i32; 3],
hi: [i32; 3],
}
#[must_use]
pub fn distance_to_chunk<R: Real>(layout: &ChunkLayout<R>, id: ChunkId, camera: [R; 3]) -> R {
let origin = layout.sample_origin(id);
let extent = R::from_f64(f64::from(layout.cells())) * layout.cell_size();
let mut squared = R::ZERO;
for axis in 0..3 {
let lo = origin[axis];
let hi = lo + extent;
let gap = if camera[axis] < lo {
lo - camera[axis]
} else if camera[axis] > hi {
camera[axis] - hi
} else {
R::ZERO
};
squared += gap * gap;
}
squared.sqrt()
}
#[cfg(test)]
mod tests;