MapEditr/src/spatial/area.rs
2021-03-09 15:44:23 -08:00

310 lines
6.9 KiB
Rust

use super::Vec3;
use std::cmp::{min, max};
pub struct AreaIterator {
min: Vec3,
max: Vec3,
pos: Vec3
}
impl AreaIterator {
#[inline]
pub fn new(min: Vec3, max: Vec3) -> Self {
Self {min, max, pos: min}
}
}
impl Iterator for AreaIterator {
type Item = Vec3;
fn next(&mut self) -> Option<Self::Item> {
if self.pos.z > self.max.z {
None
} else {
let last_pos = self.pos;
self.pos.x += 1;
if self.pos.x > self.max.x {
self.pos.x = self.min.x;
self.pos.y += 1;
if self.pos.y > self.max.y {
self.pos.y = self.min.y;
self.pos.z += 1;
}
}
Some(last_pos)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Area {
pub min: Vec3,
pub max: Vec3
}
impl Area {
pub fn is_valid(&self) -> bool {
self.min.x <= self.max.x
&& self.min.y <= self.max.y
&& self.min.z <= self.max.z
}
pub fn new(min: Vec3, max: Vec3) -> Self {
let area = Self {min, max};
assert!(area.is_valid());
area
}
pub fn from_unsorted(a: Vec3, b: Vec3) -> Self {
Self {
min: Vec3 {
x: min(a.x, b.x),
y: min(a.y, b.y),
z: min(a.z, b.z)
},
max: Vec3 {
x: max(a.x, b.x),
y: max(a.y, b.y),
z: max(a.z, b.z)
}
}
}
pub fn volume(&self) -> u64 {
(self.max.x - self.min.x + 1) as u64 *
(self.max.y - self.min.y + 1) as u64 *
(self.max.z - self.min.z + 1) as u64
}
pub fn contains(&self, pos: Vec3) -> bool {
self.min.x <= pos.x && pos.x <= self.max.x
&& self.min.y <= pos.y && pos.y <= self.max.y
&& self.min.z <= pos.z && pos.z <= self.max.z
}
pub fn contains_block(&self, block_pos: Vec3) -> bool {
let corner = block_pos * 16;
self.min.x <= corner.x && corner.x + 15 <= self.max.x
&& self.min.y <= corner.y && corner.y + 15 <= self.max.y
&& self.min.z <= corner.z && corner.z + 15 <= self.max.z
}
pub fn touches_block(&self, block_pos: Vec3) -> bool {
let corner = block_pos * 16;
self.min.x <= corner.x + 15 && corner.x <= self.max.x
&& self.min.y <= corner.y + 15 && corner.y <= self.max.y
&& self.min.z <= corner.z + 15 && corner.z <= self.max.z
}
pub fn to_contained_block_area(&self) -> Option<Self> {
let contained = Self {
min: Vec3 {
x: (self.min.x + 15).div_euclid(16),
y: (self.min.y + 15).div_euclid(16),
z: (self.min.z + 15).div_euclid(16)
},
max: Vec3 {
x: (self.max.x - 15).div_euclid(16),
y: (self.max.y - 15).div_euclid(16),
z: (self.max.z - 15).div_euclid(16)
}
};
Some(contained).filter(Self::is_valid)
}
pub fn to_touching_block_area(&self) -> Self {
Self {
min: Vec3 {
x: self.min.x.div_euclid(16),
y: self.min.y.div_euclid(16),
z: self.min.z.div_euclid(16)
},
max: Vec3 {
x: self.max.x.div_euclid(16),
y: self.max.y.div_euclid(16),
z: self.max.z.div_euclid(16)
}
}
}
pub fn abs_block_overlap(&self, block_pos: Vec3) -> Option<Self> {
let block_min = block_pos * 16;
let block_max = block_min + 15;
let overlap = Area {
min: Vec3 {
x: max(self.min.x, block_min.x),
y: max(self.min.y, block_min.y),
z: max(self.min.z, block_min.z)
},
max: Vec3 {
x: min(self.max.x, block_max.x),
y: min(self.max.y, block_max.y),
z: min(self.max.z, block_max.z)
}
};
Some(overlap).filter(Self::is_valid)
}
pub fn rel_block_overlap(&self, block_pos: Vec3) -> Option<Self> {
let corner = block_pos * 16;
let rel_min = self.min - corner;
let rel_max = self.max - corner;
let overlap = Area {
min: Vec3 {
x: max(rel_min.x, 0),
y: max(rel_min.y, 0),
z: max(rel_min.z, 0)
},
max: Vec3 {
x: min(rel_max.x, 15),
y: min(rel_max.y, 15),
z: min(rel_max.z, 15)
}
};
Some(overlap).filter(Self::is_valid)
}
}
impl IntoIterator for &Area {
type Item = Vec3;
type IntoIter = AreaIterator;
fn into_iter(self) -> Self::IntoIter {
AreaIterator::new(self.min, self.max)
}
}
impl std::ops::Add<Vec3> for Area {
type Output = Self;
fn add(self, rhs: Vec3) -> Self {
Self {
min: self.min + rhs,
max: self.max + rhs
}
}
}
impl std::ops::Sub<Vec3> for Area {
type Output = Self;
fn sub(self, rhs: Vec3) -> Self {
Self {
min: self.min - rhs,
max: self.max - rhs
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_areas() {
assert_eq!(Area {min: Vec3::new(0, 3, 1), max: Vec3::new(-1, 4, -2)}
.is_valid(), false);
assert_eq!(
Area::from_unsorted(Vec3::new(8, 0, -10), Vec3::new(-8, 0, 10)),
Area::new(Vec3::new(-8, 0, -10), Vec3::new(8, 0, 10))
);
assert_eq!(
Area::from_unsorted(Vec3::new(10, 80, 42), Vec3::new(10, -50, 99)),
Area::new(Vec3::new(10, -50, 42), Vec3::new(10, 80, 99))
);
assert_eq!(
Area::new(Vec3::new(0, 0, 0), Vec3::new(0, 0, 0)).volume(), 1);
assert_eq!(
Area::new(
Vec3::new(1, -3000, 800),
Vec3::new(4000, 999, 4799)
).volume(),
4000u64.pow(3)
);
}
#[test]
#[should_panic]
fn test_area_validity() {
Area::new(Vec3::new(0, 3, 1), Vec3::new(0, 2, 3));
}
#[test]
fn test_area_iteration() {
fn iter_area(a: Area) {
let mut iter = a.into_iter();
for z in a.min.z..=a.max.z {
for y in a.min.y..=a.max.y {
for x in a.min.x..=a.max.x {
assert_eq!(iter.next(), Some(Vec3::new(x, y, z)))
}
}
}
assert_eq!(iter.next(), None);
}
iter_area(Area::new(Vec3::new(-1, -1, -1), Vec3::new(-1, -1, -1)));
iter_area(Area::new(Vec3::new(10, -99, 11), Vec3::new(10, -99, 12)));
iter_area(Area::new(Vec3::new(0, -1, -2), Vec3::new(5, 7, 11)));
}
#[test]
fn test_area_containment() {
let area = Area::new(Vec3::new(-1, -32, 16), Vec3::new(30, -17, 54));
assert_eq!(area.contains(Vec3::new(0, -32, 32)), true);
assert_eq!(area.contains(Vec3::new(30, -32, 54)), true);
assert_eq!(area.contains(Vec3::new(30, -17, 55)), false);
assert_eq!(area.contains(Vec3::new(-2, -30, 16)), false);
let contained = Area::new(Vec3::new(0, -2, 1), Vec3::new(0, -2, 2));
let touching = Area::new(Vec3::new(-1, -2, 1), Vec3::new(1, -2, 3));
assert_eq!(area.to_contained_block_area(), Some(contained));
assert_eq!(area.to_touching_block_area(), touching);
for pos in &Area::new(touching.min - 2, touching.max + 2) {
assert_eq!(area.touches_block(pos), touching.contains(pos));
assert_eq!(area.contains_block(pos), contained.contains(pos));
}
assert_eq!(
Area::new(Vec3::new(16, 0, 1), Vec3::new(31, 15, 15))
.to_contained_block_area(),
None
);
}
#[test]
fn test_area_block_overlap() {
let area = Area::new(Vec3::new(-3, -3, -3), Vec3::new(15, 15, 15));
let pairs = [
(
Vec3::new(-1, -1, -1),
Some(Area::new(Vec3::new(-3, -3, -3), Vec3::new(-1, -1, -1)))
),
(
Vec3::new(0, 0, 0),
Some(Area::new(Vec3::new(0, 0, 0), Vec3::new(15, 15, 15)))
),
(Vec3::new(1, 1, 1), None),
(
Vec3::new(-1, 0, 0),
Some(Area::new(Vec3::new(-3, 0, 0), Vec3::new(-1, 15, 15)))
),
];
for pair in &pairs {
assert_eq!(area.abs_block_overlap(pair.0), pair.1);
assert_eq!(
area.rel_block_overlap(pair.0).map(|a| a + (pair.0 * 16)),
pair.1
);
}
}
}