use alloc::vec::Vec;
use super::{ChunkId, ChunkLayout};
use crate::cube::{corner_offset, is_inside};
use crate::{Real, Sdf};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DirtySet {
chunks: Vec<ChunkId>,
}
impl DirtySet {
#[must_use]
pub const fn new() -> Self {
Self { chunks: Vec::new() }
}
pub fn insert(&mut self, id: ChunkId) {
if let Err(at) = self.chunks.binary_search(&id) {
self.chunks.insert(at, id);
}
}
#[must_use]
pub fn len(&self) -> usize {
self.chunks.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.chunks.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = ChunkId> + '_ {
self.chunks.iter().copied()
}
pub fn clear(&mut self) {
self.chunks.clear();
}
pub fn mesh_within_budget<R, F, B>(
&mut self,
layout: &ChunkLayout<R>,
camera: [R; 3],
mut mesh: F,
mut spend: B,
) -> BudgetReport
where
R: Real,
F: FnMut(ChunkId, [R; 3]),
B: FnMut() -> bool,
{
if self.chunks.is_empty() {
return BudgetReport::default();
}
let half = layout.cell_size() * R::from_f64(f64::from(layout.cells()) * 0.5);
let mut order: Vec<(R, ChunkId)> = self
.chunks
.iter()
.map(|id| {
let base = layout.sample_origin(*id);
let mut d = R::ZERO;
for axis in 0..3 {
let centre = base[axis] + half;
let delta = centre - camera[axis];
d += delta * delta;
}
(d, *id)
})
.collect();
order.sort_by(|a, b| a.0.total_cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
let mut meshed = 0usize;
for (_, id) in &order {
mesh(*id, layout.sample_origin(*id));
meshed += 1;
if !spend() {
break;
}
}
let mut kept: Vec<ChunkId> = order[meshed..].iter().map(|(_, id)| *id).collect();
kept.sort_unstable();
self.chunks = kept;
BudgetReport {
meshed,
remaining: self.chunks.len(),
}
}
pub fn mesh_dirty<R, F>(&mut self, layout: &ChunkLayout<R>, mut mesh: F) -> usize
where
R: Real,
F: FnMut(ChunkId, [R; 3]),
{
for id in &self.chunks {
mesh(*id, layout.sample_origin(*id));
}
let done = self.chunks.len();
self.chunks.clear();
done
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BudgetReport {
pub meshed: usize,
pub remaining: usize,
}
impl BudgetReport {
#[must_use]
pub fn is_drained(&self) -> bool {
self.remaining == 0
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct EditReport {
pub region_cells: u64,
pub value_changed_cells: u64,
pub output_changed_cells: u64,
pub sign_changed_cells: u64,
pub swept_cells: u64,
pub region_chunks: u64,
pub dirty_chunks: u64,
}
impl EditReport {
#[must_use]
pub fn changed_fraction(&self) -> f64 {
if self.region_cells == 0 {
0.0
} else {
self.output_changed_cells as f64 / self.region_cells as f64
}
}
#[must_use]
pub fn value_changed_fraction(&self) -> f64 {
if self.region_cells == 0 {
0.0
} else {
self.value_changed_cells as f64 / self.region_cells as f64
}
}
#[must_use]
pub fn dirty_chunk_fraction(&self) -> f64 {
if self.region_chunks == 0 {
0.0
} else {
self.dirty_chunks as f64 / self.region_chunks as f64
}
}
}
pub fn mark_edit<R, A, B>(
layout: &ChunkLayout<R>,
before: &A,
after: &B,
min_cell: [i64; 3],
max_cell: [i64; 3],
dirty: &mut DirtySet,
) -> EditReport
where
R: Real,
A: Sdf<Scalar = R>,
B: Sdf<Scalar = R>,
{
let mut report = EditReport::default();
for axis in 0..3 {
if max_cell[axis] < min_cell[axis] {
return report;
}
}
let extent = [
(max_cell[0] - min_cell[0] + 2) as usize,
(max_cell[1] - min_cell[1] + 2) as usize,
(max_cell[2] - min_cell[2] + 2) as usize,
];
let count = extent[0] * extent[1] * extent[2];
let mut changed = alloc::vec![false; count];
let mut inside_before = alloc::vec![false; count];
let mut inside_after = alloc::vec![false; count];
let index = |x: usize, y: usize, z: usize| x + extent[0] * (y + extent[1] * z);
for z in 0..extent[2] {
for y in 0..extent[1] {
for x in 0..extent[0] {
let sample = [
min_cell[0] + x as i64,
min_cell[1] + y as i64,
min_cell[2] + z as i64,
];
let p = layout.world_of_sample(sample);
let a = before.sample(p);
let b = after.sample(p);
let i = index(x, y, z);
changed[i] = a.total_cmp(&b) != core::cmp::Ordering::Equal;
inside_before[i] = is_inside(a);
inside_after[i] = is_inside(b);
}
}
}
let mut region_chunks = DirtySet::new();
for cz in 0..extent[2] - 1 {
for cy in 0..extent[1] - 1 {
for cx in 0..extent[0] - 1 {
report.region_cells += 1;
let mut any_value = false;
let mut any_sign = false;
let mut inside_count_before = 0u32;
let mut inside_count_after = 0u32;
for corner in 0..8u8 {
let o = corner_offset(corner);
let i = index(cx + o[0] as usize, cy + o[1] as usize, cz + o[2] as usize);
any_value |= changed[i];
any_sign |= inside_before[i] != inside_after[i];
inside_count_before += u32::from(inside_before[i]);
inside_count_after += u32::from(inside_after[i]);
}
let was_surface = inside_count_before != 0 && inside_count_before != 8;
let is_surface = inside_count_after != 0 && inside_count_after != 8;
let cell = [
min_cell[0] + cx as i64,
min_cell[1] + cy as i64,
min_cell[2] + cz as i64,
];
let (owner, _) = layout.local_sample(cell);
region_chunks.insert(owner);
if any_value {
report.value_changed_cells += 1;
}
if any_value && (was_surface || is_surface) {
report.output_changed_cells += 1;
dirty.insert(owner);
}
if any_sign {
report.sign_changed_cells += 1;
if !was_surface && !is_surface {
report.swept_cells += 1;
}
}
}
}
}
report.region_chunks = region_chunks.len() as u64;
report.dirty_chunks = region_chunks
.iter()
.filter(|id| dirty.iter().any(|d| d == *id))
.count() as u64;
report
}
#[cfg(test)]
mod tests;