use nalgebra::{Point2, Vector2, point, vector};
use crate::{TileIndex, TileIndexOffset};
#[derive(Clone, Copy, Debug)]
pub struct TileRect {
pub origin: TileIndex,
pub size: Vector2<usize>,
}
impl Default for TileRect {
fn default() -> Self {
Self {
origin: point![0, 0],
size: vector![0, 0],
}
}
}
pub struct Rect {
x: f32,
y: f32,
w: f32,
h: f32,
}
impl Rect {
pub fn left(&self) -> f32 {
self.x
}
pub fn right(&self) -> f32 {
self.x + self.w
}
pub fn top(&self) -> f32 {
self.y
}
pub fn bottom(&self) -> f32 {
self.y + self.h
}
}
impl TileRect {
pub fn from_rect_inclusive(rect: Rect) -> TileRect {
TileRect::from_rect(rect, f32::floor, f32::ceil)
}
pub fn from_rect_exclusive(rect: Rect) -> TileRect {
TileRect::from_rect(rect, f32::ceil, f32::floor)
}
pub fn from_rect_round_to_min(rect: Rect) -> TileRect {
TileRect::from_rect(rect, f32::floor, f32::floor)
}
fn from_rect(rect: Rect, min_fn: impl Fn(f32) -> f32, max_fn: impl Fn(f32) -> f32) -> TileRect {
let min_corner = point![min_fn(rect.left()) as isize, min_fn(rect.top()) as isize];
let max_corner = point![
max_fn(rect.right()) as isize,
max_fn(rect.bottom()) as isize
];
TileRect {
origin: min_corner,
size: (max_corner - min_corner).map(|x| x.max(0) as usize),
}
}
#[must_use]
pub fn translate(&self, translation: TileIndexOffset) -> Self {
Self {
origin: self.origin + translation,
size: self.size,
}
}
pub fn linear_index_to_tile_index(&self, linear_index: usize) -> Option<TileIndex> {
if linear_index < self.area() {
let x_offset = (linear_index % self.size.x) as isize;
let y_offset = (linear_index / self.size.x) as isize;
Some(self.origin + vector![x_offset, y_offset])
} else {
None
}
}
pub fn min_corner(&self) -> TileIndex {
self.origin
}
pub fn max_corner(&self) -> TileIndex {
Vector2::from_fn(|i, _| self.origin[i].checked_add_unsigned(self.size[i]).unwrap()).into()
}
pub fn left(&self) -> isize {
self.origin.x
}
pub fn right(&self) -> isize {
self.origin.x.wrapping_add_unsigned(self.size.x) - 1
}
pub fn top(&self) -> isize {
self.origin.y
}
pub fn bottom(&self) -> isize {
self.origin.y.wrapping_add_unsigned(self.size.y) - 1
}
pub fn intersects(&self, rhs: &TileRect) -> bool {
self.left() <= rhs.right()
&& rhs.left() <= self.right()
&& self.top() <= rhs.bottom()
&& rhs.top() <= self.bottom()
}
pub fn contains_point(&self, point: TileIndex) -> bool {
self.left() <= point.x
&& self.right() >= point.x
&& self.top() <= point.y
&& self.bottom() >= point.y
}
pub fn contains(&self, rhs: &TileRect) -> bool {
self.left() <= rhs.left()
&& self.right() >= rhs.right()
&& self.top() <= rhs.top()
&& self.bottom() >= rhs.bottom()
}
pub fn area(&self) -> usize {
self.size.x * self.size.y
}
pub fn is_empty(&self) -> bool {
self.size.x == 0 || self.size.y == 0
}
pub fn expand_to_include_index(
&mut self,
index: TileIndex,
minimum_nonzero_expansion: Vector2<usize>,
) -> bool {
self.expand_to_include_bounds(
TileRect {
origin: index,
size: vector![1, 1],
},
minimum_nonzero_expansion,
)
}
pub fn expand_to_include_bounds(
&mut self,
bounds: TileRect,
minimum_nonzero_expansion: Vector2<usize>,
) -> bool {
let mut expanded = false;
let min_expansion = minimum_nonzero_expansion;
if self.is_empty() {
expanded = true;
*self = bounds;
}
let origin: Point2<_> = Vector2::from_fn(|i, _| {
if bounds.origin[i] < self.origin[i] {
expanded = true;
bounds.origin[i].min(self.origin[i].saturating_sub(min_expansion[i] as isize))
} else {
self.origin[i]
}
})
.into();
let end_corner = self.max_corner();
let bounds_end_corner = bounds.max_corner();
let end_corner: Point2<_> = Vector2::from_fn(|i, _| {
if bounds_end_corner[i] > end_corner[i] {
expanded = true;
bounds_end_corner[i].max(end_corner[i].saturating_add(min_expansion[i] as isize))
} else {
end_corner[i]
}
})
.into();
self.origin = origin;
self.size = Vector2::from_fn(|i, _| origin[i].abs_diff(end_corner[i]));
expanded
}
}
pub struct Indices {
bounds: TileRect,
linear_index: usize,
}
impl Iterator for Indices {
type Item = TileIndex;
fn next(&mut self) -> Option<Self::Item> {
if let Some(index) = self.bounds.linear_index_to_tile_index(self.linear_index) {
self.linear_index += 1;
Some(index)
} else {
None
}
}
}
impl TileRect {
pub fn indices(&self) -> Indices {
Indices {
bounds: *self,
linear_index: 0,
}
}
}