use itertools::Itertools;
use glam::{IVec2, UVec2, Vec2};
use crate::Pivot;
#[derive(Default, Debug, Clone)]
pub struct WorldGrid {
pub world_pos: Vec2,
size: UVec2,
center_offset: Vec2,
pos_offset: Vec2,
pivot_offset: Vec2,
axis: Vec2,
}
impl WorldGrid {
pub fn new(world_pos: (f32, f32), size: [u32; 2], pivot: Pivot) -> Self {
let world_pos = Vec2::from(world_pos);
let size = UVec2::from(size);
let center_offset = match pivot {
Pivot::Center => Vec2::select(
(size % 2).cmpeq(UVec2::ZERO),
Vec2::new(0.5, 0.5),
Vec2::ZERO,
),
_ => Vec2::new(0.5, 0.5),
};
let pos_offset = center_offset - Vec2::new(0.5, 0.5);
let axis = pivot.axis().as_vec2();
let pivot_offset = match pivot {
Pivot::Center => -size.as_vec2() * Vec2::new(0.5, 0.5),
_ => Vec2::ZERO,
};
WorldGrid {
world_pos,
size,
center_offset,
pivot_offset,
pos_offset,
axis,
}
}
pub fn origin(size: [u32; 2], pivot: Pivot) -> Self {
WorldGrid::new((0.0, 0.0), size, pivot)
}
#[inline]
pub fn tile_pos(&self, grid_pos: [i32; 2]) -> Vec2 {
let grid_pos = IVec2::from(grid_pos).as_vec2();
grid_pos + self.pos_offset
}
#[inline]
pub fn tile_center(&self, grid_pos: [i32; 2]) -> Vec2 {
let grid_pos = IVec2::from(grid_pos).as_vec2();
grid_pos + self.center_offset
}
#[inline]
pub fn tile_pos_world(&self, grid_pos: [i32; 2]) -> Vec2 {
self.tile_pos(grid_pos) + self.world_pos
}
#[inline]
pub fn tile_center_world(&self, grid_pos: [i32; 2]) -> Vec2 {
self.tile_center(grid_pos) + self.world_pos
}
#[inline]
pub fn grid_pos_in_bounds(&self, grid_pos: [i32; 2]) -> bool {
self.try_grid_to_index_2d(grid_pos).is_some()
}
#[inline]
pub fn index_2d_in_bounds(&self, index: (u32, u32)) -> bool {
let index = UVec2::from(index);
index.cmpge(UVec2::ZERO).all() && index.cmplt(self.size()).all()
}
pub fn try_grid_to_index_2d(&self, grid_pos: [i32; 2]) -> Option<UVec2> {
let center = self.tile_center(grid_pos);
let index = center * self.axis - self.pivot_offset;
if index.cmpge(Vec2::ZERO).all() && index.cmplt(self.size.as_vec2()).all() {
return Some(index.as_uvec2());
};
None
}
pub fn grid_to_index_2d(&self, grid_pos: [i32; 2]) -> UVec2 {
let center = self.tile_center(grid_pos);
let index = center * self.axis - self.pivot_offset;
index.as_uvec2()
}
pub fn index_2d_to_grid(&self, i: [u32; 2]) -> IVec2 {
let p = UVec2::from(i).as_vec2();
let p = (self.pivot_offset - self.pos_offset) + p;
let p = p + self.center_offset;
let p = (p * self.axis).floor();
p.as_ivec2()
}
pub fn width(&self) -> usize {
self.size.x as usize
}
pub fn height(&self) -> usize {
self.size.y as usize
}
pub fn size(&self) -> UVec2 {
self.size
}
pub fn tile_pos_iter(&self) -> impl Iterator<Item = Vec2> {
self.iter(self.pivot_offset, self.axis)
}
pub fn tile_center_iter(&self) -> impl Iterator<Item = Vec2> {
self.iter(self.pivot_offset + Vec2::new(0.5, 0.5), self.axis)
}
pub fn tile_pos_iter_world(&self) -> impl Iterator<Item = Vec2> {
let offset = self.world_pos;
self.iter(self.pivot_offset + offset, self.axis)
}
#[inline]
fn iter(&self, offset: Vec2, axis: Vec2) -> impl Iterator<Item = Vec2> {
(0..self.height())
.cartesian_product(0..self.width())
.map(move |(y, x)| (Vec2::new(x as f32, y as f32) + offset) * axis)
}
}
#[cfg(test)]
mod test {
use crate::Pivot;
use super::WorldGrid;
#[test]
fn center_iter_odd() {
let grid = WorldGrid::origin([3, 3], Pivot::Center);
let tiles: Vec<_> = grid.tile_center_iter().map(|p| (p.x, p.y)).collect();
assert_eq!(tiles.len(), 9);
assert_eq!(tiles[0], (-1.0, -1.0));
assert_eq!(tiles[1], (0.0, -1.0));
assert_eq!(tiles[2], (1.0, -1.0));
assert_eq!(tiles[3], (-1.0, 0.0));
assert_eq!(tiles[4], (0.0, 0.0));
assert_eq!(tiles[8], (1.0, 1.0));
}
#[test]
fn center_iter_even() {
let grid = WorldGrid::origin([10, 10], Pivot::Center);
let tiles: Vec<_> = grid.tile_center_iter().map(|p| (p.x, p.y)).collect();
assert_eq!(tiles.len(), 100);
assert_eq!(tiles[0], (-4.5, -4.5));
assert_eq!(tiles[1], (-3.5, -4.5));
assert_eq!(tiles[2], (-2.5, -4.5));
assert_eq!(tiles[3], (-1.5, -4.5));
assert_eq!(tiles[99], (4.5, 4.5));
let tiles: Vec<_> = grid.tile_pos_iter().map(|p| (p.x, p.y)).collect();
assert_eq!(tiles.len(), 100);
assert_eq!(tiles[0], (-5.0, -5.0));
assert_eq!(tiles[1], (-4.0, -5.0));
assert_eq!(tiles[2], (-3.0, -5.0));
assert_eq!(tiles[3], (-2.0, -5.0));
assert_eq!(tiles[99], (4.0, 4.0));
}
#[test]
fn bottom_left_iter_odd() {
let grid = WorldGrid::origin([5, 5], Pivot::BottomLeft);
let tiles: Vec<_> = grid.tile_center_iter().map(|p| (p.x, p.y)).collect();
assert_eq!(tiles.len(), 25);
assert_eq!(tiles[0], (0.5, 0.5));
assert_eq!(tiles[1], (1.5, 0.5));
assert_eq!(tiles[2], (2.5, 0.5));
assert_eq!(tiles[3], (3.5, 0.5));
assert_eq!(tiles[24], (4.5, 4.5));
let tiles: Vec<_> = grid.tile_pos_iter().map(|p| (p.x, p.y)).collect();
assert_eq!(tiles[0], (0.0, 0.0));
assert_eq!(tiles[1], (1.0, 0.0));
assert_eq!(tiles[2], (2.0, 0.0));
assert_eq!(tiles[3], (3.0, 0.0));
assert_eq!(tiles[24], (4.0, 4.0));
}
#[test]
fn top_left_iter_odd() {
let grid = WorldGrid::origin([5, 5], Pivot::TopLeft);
let tiles: Vec<_> = grid.tile_center_iter().map(|p| (p.x, p.y)).collect();
assert_eq!(tiles.len(), 25);
assert_eq!(tiles[0], (0.5, -0.5));
assert_eq!(tiles[1], (1.5, -0.5));
assert_eq!(tiles[2], (2.5, -0.5));
assert_eq!(tiles[3], (3.5, -0.5));
assert_eq!(tiles[24], (4.5, -4.5));
}
#[test]
fn bottom_right_iter_odd() {
let grid = WorldGrid::origin([5, 5], Pivot::BottomRight);
let tiles: Vec<_> = grid.tile_center_iter().map(|p| (p.x, p.y)).collect();
assert_eq!(tiles.len(), 25);
assert_eq!(tiles[0], (-0.5, 0.5));
assert_eq!(tiles[1], (-1.5, 0.5));
assert_eq!(tiles[2], (-2.5, 0.5));
assert_eq!(tiles[3], (-3.5, 0.5));
assert_eq!(tiles[24], (-4.5, 4.5));
}
#[test]
fn center_bounds() {
let grid = WorldGrid::origin([5, 5], Pivot::Center);
assert!(!grid.grid_pos_in_bounds([-3, -3]));
assert!(grid.grid_pos_in_bounds([-2, -2]));
assert!(grid.grid_pos_in_bounds([-1, -1]));
assert!(grid.grid_pos_in_bounds([0, 0]));
assert!(grid.grid_pos_in_bounds([1, 1]));
assert!(grid.grid_pos_in_bounds([2, 2]));
}
#[test]
fn bottom_left_bounds() {
let grid = WorldGrid::origin([5, 5], Pivot::BottomLeft);
assert!(!grid.grid_pos_in_bounds([-1, -1]));
assert!(grid.grid_pos_in_bounds([0, 0]));
assert!(grid.grid_pos_in_bounds([1, 1]));
assert!(grid.grid_pos_in_bounds([2, 2]));
assert!(grid.grid_pos_in_bounds([3, 3]));
assert!(grid.grid_pos_in_bounds([4, 4]));
assert!(!grid.grid_pos_in_bounds([5, 5]));
}
#[test]
fn top_right_bounds() {
let grid = WorldGrid::origin([5, 5], Pivot::TopRight);
assert!(!grid.grid_pos_in_bounds([1, 1]));
assert!(!grid.grid_pos_in_bounds([0, 0]));
assert!(!grid.grid_pos_in_bounds([0, -1]));
assert!(grid.grid_pos_in_bounds([-1, -2]));
assert!(grid.grid_pos_in_bounds([-2, -3]));
assert!(grid.grid_pos_in_bounds([-4, -4]));
}
#[test]
fn top_right_grid_to_index_2d() {
let grid = WorldGrid::origin([5, 5], Pivot::TopRight);
assert_eq!([0, 0], grid.grid_to_index_2d([-1, -1]).to_array());
assert_eq!([1, 1], grid.grid_to_index_2d([-2, -2]).to_array());
assert_eq!([2, 2], grid.grid_to_index_2d([-3, -3]).to_array());
}
#[test]
fn top_right_grid_to_grid() {
let grid = WorldGrid::origin([5, 5], Pivot::TopRight);
assert_eq!([-1, -1], grid.index_2d_to_grid([0, 0]).to_array());
assert_eq!([-2, -2], grid.index_2d_to_grid([1, 1]).to_array());
assert_eq!([-3, -3], grid.index_2d_to_grid([2, 2]).to_array());
}
#[test]
fn top_left_grid_to_grid() {
let grid = WorldGrid::origin([5, 5], Pivot::TopLeft);
assert_eq!([0, -1], grid.index_2d_to_grid([0, 0]).to_array());
assert_eq!([1, -2], grid.index_2d_to_grid([1, 1]).to_array());
assert_eq!([2, -3], grid.index_2d_to_grid([2, 2]).to_array());
}
#[test]
fn bottom_left_grid_to_grid() {
let grid = WorldGrid::origin([5, 5], Pivot::BottomLeft);
assert_eq!([0, 0], grid.index_2d_to_grid([0, 0]).to_array());
}
#[test]
fn top_left_grid_to_index_2d() {
let grid = WorldGrid::origin([5, 5], Pivot::TopLeft);
assert_eq!([0, 0], grid.grid_to_index_2d([0, -1]).to_array());
assert_eq!([1, 1], grid.grid_to_index_2d([1, -2]).to_array());
assert_eq!([2, 2], grid.grid_to_index_2d([2, -3]).to_array());
}
#[test]
fn bottom_left_grid_to_index_2d() {
let grid = WorldGrid::origin([5, 5], Pivot::BottomLeft);
assert_eq!([0, 0], grid.grid_to_index_2d([0, 0]).to_array());
assert_eq!([1, 1], grid.grid_to_index_2d([1, 1]).to_array());
assert_eq!([2, 2], grid.grid_to_index_2d([2, 2]).to_array());
}
#[test]
fn center_grid_to_index_2d() {
let grid = WorldGrid::origin([5, 5], Pivot::Center);
assert_eq!([0, 0], grid.grid_to_index_2d([-2, -2]).to_array());
assert_eq!([1, 1], grid.grid_to_index_2d([-1, -1]).to_array());
assert_eq!([2, 2], grid.grid_to_index_2d([0, 0]).to_array());
assert_eq!([3, 3], grid.grid_to_index_2d([1, 1]).to_array());
assert_eq!([4, 4], grid.grid_to_index_2d([2, 2]).to_array());
}
#[test]
fn center_grid_to_grid() {
let grid = WorldGrid::origin([5, 5], Pivot::Center);
assert_eq!([-2, -2], grid.index_2d_to_grid([0, 0]).to_array());
assert_eq!([-1, -1], grid.index_2d_to_grid([1, 1]).to_array());
assert_eq!([0, 0], grid.index_2d_to_grid([2, 2]).to_array());
assert_eq!([1, 1], grid.index_2d_to_grid([3, 3]).to_array());
assert_eq!([2, 2], grid.index_2d_to_grid([4, 4]).to_array());
}
#[test]
fn top_right_tile_center() {
let grid = WorldGrid::origin([3, 3], Pivot::TopRight);
assert_eq!([-0.5, -0.5], grid.tile_center([-1, -1]).to_array());
assert_eq!([-1.5, -1.5], grid.tile_center([-2, -2]).to_array());
}
#[test]
fn top_left_tile_center() {
let grid = WorldGrid::origin([3, 3], Pivot::TopLeft);
assert_eq!([0.5, -0.5], grid.tile_center([0, -1]).to_array());
assert_eq!([1.5, -1.5], grid.tile_center([1, -2]).to_array());
}
#[test]
fn center_tile_center_odd() {
let grid = WorldGrid::origin([3, 3], Pivot::Center);
assert_eq!([0.0, 0.0], grid.tile_center([0, 0]).to_array());
assert_eq!([-1.0, -1.0], grid.tile_center([-1, -1]).to_array());
}
#[test]
fn center_tile_center_even() {
let grid = WorldGrid::origin([4, 4], Pivot::Center);
assert_eq!([0.5, 0.5], grid.tile_center([0, 0]).to_array());
assert_eq!([1.5, 1.5], grid.tile_center([1, 1]).to_array());
}
#[test]
fn center_tile_pos_odd() {
let grid = WorldGrid::origin([3, 3], Pivot::Center);
assert_eq!([-0.5, -0.5], grid.tile_pos([0, 0]).to_array());
assert_eq!([-1.5, -1.5], grid.tile_pos([-1, -1]).to_array());
}
#[test]
fn center_tile_pos_even() {
let grid = WorldGrid::origin([4, 4], Pivot::Center);
assert_eq!([0.0, 0.0], grid.tile_pos([0, 0]).to_array());
assert_eq!([-1.0, -1.0], grid.tile_pos([-1, -1]).to_array());
}
}