use glam::{DVec3, IVec3};
use serde::{Deserialize, Serialize};
use crate::{GridId, Scene};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct WaterVolume {
pub lo: IVec3,
pub hi: IVec3,
}
impl WaterVolume {
#[must_use]
pub fn new(a: IVec3, b: IVec3) -> Self {
Self {
lo: a.min(b),
hi: a.max(b),
}
}
#[must_use]
pub fn depth_local(&self, p: DVec3) -> Option<f64> {
let lo = self.lo.min(self.hi).as_dvec3();
let hi = self.lo.max(self.hi).as_dvec3() + DVec3::ONE; let inside =
p.x >= lo.x && p.x < hi.x && p.y >= lo.y && p.y < hi.y && p.z >= lo.z && p.z < hi.z;
inside.then_some(p.z - lo.z)
}
}
impl crate::Grid {
pub fn add_water_volume(&mut self, a: IVec3, b: IVec3) {
self.water_volumes.push(WaterVolume::new(a, b));
}
#[must_use]
pub fn water_depth_local(&self, p: DVec3) -> Option<f64> {
self.water_volumes
.iter()
.filter_map(|v| v.depth_local(p))
.max_by(f64::total_cmp)
}
}
impl Scene {
#[must_use]
pub fn water_depth_at(&self, world: DVec3) -> Option<(GridId, f64)> {
self.grids()
.filter(|(_, grid)| !grid.water_volumes.is_empty())
.filter_map(|(id, grid)| {
let local = crate::streaming::world_to_grid_local_pos(world, &grid.transform);
let d_local = grid.water_depth_local(local)?;
Some((id, d_local * grid.transform.voxel_world_size))
})
.max_by(|a, b| a.1.total_cmp(&b.1).then_with(|| b.0.cmp(&a.0)))
}
#[must_use]
pub fn in_water(&self, world: DVec3) -> bool {
self.grids()
.filter(|(_, grid)| !grid.water_volumes.is_empty())
.any(|(_, grid)| {
let local = crate::streaming::world_to_grid_local_pos(world, &grid.transform);
grid.water_depth_local(local).is_some()
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::GridTransform;
use glam::DQuat;
#[test]
fn corners_normalise_and_span_is_inclusive() {
let v = WaterVolume::new(IVec3::new(9, 9, 9), IVec3::new(2, 3, 4));
assert_eq!(v.lo, IVec3::new(2, 3, 4));
assert_eq!(v.hi, IVec3::new(9, 9, 9));
assert!(v.depth_local(DVec3::new(9.5, 9.5, 9.5)).is_some());
assert!(v.depth_local(DVec3::new(10.0, 9.5, 9.5)).is_none());
}
#[test]
fn depth_is_zero_at_surface_and_grows_downward() {
let v = WaterVolume::new(IVec3::new(0, 0, 10), IVec3::new(7, 7, 19));
assert_eq!(v.depth_local(DVec3::new(4.0, 4.0, 10.0)), Some(0.0));
assert_eq!(v.depth_local(DVec3::new(4.0, 4.0, 15.5)), Some(5.5));
assert!(v.depth_local(DVec3::new(4.0, 4.0, 9.9)).is_none());
assert!(v.depth_local(DVec3::new(4.0, 4.0, 20.0)).is_none());
}
#[test]
fn grid_takes_deepest_of_overlapping_volumes() {
let mut g = crate::Grid::new(GridTransform::identity());
g.add_water_volume(IVec3::new(0, 0, 10), IVec3::new(9, 9, 19));
g.add_water_volume(IVec3::new(0, 0, 14), IVec3::new(9, 9, 19));
assert_eq!(g.water_depth_local(DVec3::new(5.0, 5.0, 16.0)), Some(6.0));
}
#[test]
fn scene_world_query_identity_grid() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::new(100.0, 0.0, 0.0)));
scene
.grid_mut(id)
.unwrap()
.add_water_volume(IVec3::new(0, 0, 50), IVec3::new(31, 31, 63));
assert_eq!(
scene.water_depth_at(DVec3::new(110.0, 5.0, 55.0)),
Some((id, 5.0))
);
assert!(scene.water_depth_at(DVec3::new(110.0, 5.0, 49.0)).is_none());
assert!(!scene.in_water(DVec3::new(0.0, 0.0, 55.0))); }
#[test]
fn scene_world_query_scales_with_vws() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at_scale(DVec3::ZERO, 0.5));
scene
.grid_mut(id)
.unwrap()
.add_water_volume(IVec3::new(0, 0, 20), IVec3::new(15, 15, 39));
assert_eq!(
scene.water_depth_at(DVec3::new(3.0, 3.0, 14.0)),
Some((id, 4.0))
);
assert!(scene.water_depth_at(DVec3::new(3.0, 3.0, 9.9)).is_none());
}
#[test]
fn scene_world_query_rotated_grid() {
let mut scene = Scene::new();
let rot = DQuat::from_rotation_z(std::f64::consts::FRAC_PI_2);
let id = scene.add_grid(GridTransform {
origin: DVec3::ZERO,
rotation: rot,
..GridTransform::identity()
});
scene
.grid_mut(id)
.unwrap()
.add_water_volume(IVec3::new(0, 0, 8), IVec3::new(9, 19, 15));
let world = rot * DVec3::new(5.0, 10.0, 12.0);
assert_eq!(scene.water_depth_at(world), Some((id, 4.0)));
assert!(scene.water_depth_at(DVec3::new(5.0, 10.0, 12.0)).is_none());
}
#[test]
fn swapped_corners_query_like_canonical_ones() {
let swapped = WaterVolume {
lo: IVec3::new(7, 7, 19),
hi: IVec3::new(0, 0, 10),
};
let canonical = WaterVolume::new(IVec3::new(0, 0, 10), IVec3::new(7, 7, 19));
for p in [
DVec3::new(4.0, 4.0, 10.0),
DVec3::new(4.0, 4.0, 15.5),
DVec3::new(4.0, 4.0, 9.9),
DVec3::new(8.5, 4.0, 15.0),
] {
assert_eq!(swapped.depth_local(p), canonical.depth_local(p));
}
}
#[test]
fn equal_depth_tie_breaks_to_smallest_grid_id() {
let mut scene = Scene::new();
let a = scene.add_grid(GridTransform::identity());
let b = scene.add_grid(GridTransform::identity());
for id in [a, b] {
scene
.grid_mut(id)
.unwrap()
.add_water_volume(IVec3::new(0, 0, 50), IVec3::new(31, 31, 63));
}
let winner = scene.water_depth_at(DVec3::new(5.0, 5.0, 55.0));
assert_eq!(winner, Some((a.min(b), 5.0)), "smallest GridId wins ties");
}
#[test]
fn no_volumes_means_dry_everywhere() {
let mut scene = Scene::new();
scene.add_grid(GridTransform::identity());
assert!(scene.water_depth_at(DVec3::new(1.0, 2.0, 3.0)).is_none());
assert!(!scene.in_water(DVec3::splat(50.0)));
}
}