bevy_entitiles 0.3.0

A 2d tilemap library for bevy. With many useful algorithms/tools built in.
use bevy::{
    math::IVec2,
    prelude::{UVec2, Vec2},
};

use crate::tilemap::map::TilemapType;

pub trait F32Integerize {
    fn round_to_i32(self) -> i32;
    fn ceil_to_i32(self) -> i32;
    fn floor_to_i32(self) -> i32;
    fn round_to_u32(self) -> u32;
    fn ceil_to_u32(self) -> u32;
    fn floor_to_u32(self) -> u32;
}

impl F32Integerize for f32 {
    #[inline]
    fn round_to_i32(self) -> i32 {
        self.round() as i32
    }

    #[inline]
    fn ceil_to_i32(self) -> i32 {
        self.ceil() as i32
    }

    #[inline]
    fn floor_to_i32(self) -> i32 {
        self.floor() as i32
    }

    #[inline]
    fn round_to_u32(self) -> u32 {
        (self + 0.5) as u32
    }

    #[inline]
    fn ceil_to_u32(self) -> u32 {
        self as u32 + 1
    }

    #[inline]
    fn floor_to_u32(self) -> u32 {
        self as u32
    }
}

pub trait Vec2Integerize {
    fn round_to_ivec(self) -> IVec2;
    fn ceil_to_ivec(self) -> IVec2;
    fn floor_to_ivec(self) -> IVec2;
    fn round_to_uvec(self) -> UVec2;
    fn ceil_to_uvec(self) -> UVec2;
    fn floor_to_uvec(self) -> UVec2;
}

impl Vec2Integerize for Vec2 {
    #[inline]
    fn round_to_ivec(self) -> IVec2 {
        self.round().as_ivec2()
    }

    #[inline]
    fn ceil_to_ivec(self) -> IVec2 {
        self.ceil().as_ivec2()
    }

    #[inline]
    fn floor_to_ivec(self) -> IVec2 {
        self.floor().as_ivec2()
    }

    #[inline]
    fn round_to_uvec(self) -> UVec2 {
        UVec2 {
            x: self.x.round_to_u32(),
            y: self.y.round_to_u32(),
        }
    }

    #[inline]
    fn ceil_to_uvec(self) -> UVec2 {
        UVec2 {
            x: self.x.ceil_to_u32(),
            y: self.y.ceil_to_u32(),
        }
    }

    #[inline]
    fn floor_to_uvec(self) -> UVec2 {
        UVec2 {
            x: self.x as u32,
            y: self.y as u32,
        }
    }
}

pub trait ManhattanDistance<T> {
    fn manhattan_distance(self, other: Self) -> T;
}

impl ManhattanDistance<u32> for UVec2 {
    fn manhattan_distance(self, other: Self) -> u32 {
        let d = (self.as_ivec2() - other.as_ivec2()).abs().as_uvec2();
        d.x + d.y
    }
}

impl ManhattanDistance<u32> for IVec2 {
    fn manhattan_distance(self, other: Self) -> u32 {
        let d = (self - other).abs();
        d.x as u32 + d.y as u32
    }
}

pub trait TileIndex<T> {
    fn neighbours(self, ty: TilemapType, allow_diagonal: bool) -> Vec<Option<T>>;
}

impl TileIndex<IVec2> for IVec2 {
    fn neighbours(self, ty: TilemapType, allow_diagonal: bool) -> Vec<Option<IVec2>> {
        match ty {
            TilemapType::Hexagonal(_) => [
                IVec2::ONE,
                IVec2::ONE,
                IVec2::NEG_ONE,
                IVec2::NEG_ONE,
                IVec2::X,
                IVec2::Y,
            ]
            .into_iter()
            .map(|p| Some(p + self))
            .collect(),
            _ => {
                let seq = [
                    IVec2::Y,
                    IVec2::X,
                    IVec2::NEG_X,
                    IVec2::NEG_Y,
                    IVec2::ONE,
                    IVec2::NEG_ONE,
                    IVec2::new(1, -1),
                    IVec2::new(-1, 1),
                ]
                .into_iter()
                .map(|p| Some(p + self))
                .collect();
                if allow_diagonal {
                    seq
                } else {
                    seq[0..4].to_vec()
                }
            }
        }
    }
}

impl TileIndex<UVec2> for UVec2 {
    fn neighbours(self, ty: TilemapType, allow_diagonal: bool) -> Vec<Option<UVec2>> {
        match ty {
            TilemapType::Hexagonal(_) => [
                IVec2::ONE,
                IVec2::ONE,
                IVec2::NEG_ONE,
                IVec2::NEG_ONE,
                IVec2::X,
                IVec2::Y,
            ]
            .into_iter()
            .map(|p| {
                let nei = p + self.as_ivec2();
                if nei.x >= 0 && nei.y >= 0 {
                    Some(nei.as_uvec2())
                } else {
                    None
                }
            })
            .collect(),
            _ => {
                let seq = [
                    IVec2::Y,
                    IVec2::X,
                    IVec2::NEG_X,
                    IVec2::NEG_Y,
                    IVec2::ONE,
                    IVec2::NEG_ONE,
                    IVec2::new(1, -1),
                    IVec2::new(-1, 1),
                ]
                .into_iter()
                .map(|p| {
                    let nei = p + self.as_ivec2();
                    if nei.x >= 0 && nei.y >= 0 {
                        Some(nei.as_uvec2())
                    } else {
                        None
                    }
                })
                .collect();
                if allow_diagonal {
                    seq
                } else {
                    seq[0..4].to_vec()
                }
            }
        }
    }
}

pub trait DivToCeil {
    fn div_to_ceil(self, other: Self) -> Self;
}

impl DivToCeil for IVec2 {
    fn div_to_ceil(self, other: Self) -> Self {
        let mut result = self / other;
        if self.x % other.x != 0 && self.x > 0 {
            result.x += 1;
        }
        if self.y % other.y != 0 && self.x > 0 {
            result.y += 1;
        }
        result
    }
}

impl DivToCeil for UVec2 {
    fn div_to_ceil(self, other: Self) -> Self {
        let mut result = self / other;
        if self.x % other.x != 0 {
            result.x += 1;
        }
        if self.y % other.y != 0 {
            result.y += 1;
        }
        result
    }
}

pub trait DivToFloor {
    fn div_to_floor(self, other: Self) -> Self;
}

impl DivToFloor for IVec2 {
    fn div_to_floor(self, other: Self) -> Self {
        let mut result = self / other;
        if self.x % other.x != 0 && self.x < 0 {
            result.x -= 1;
        }
        if self.y % other.y != 0 && self.y < 0 {
            result.y -= 1;
        }
        result
    }
}

impl DivToFloor for UVec2 {
    fn div_to_floor(self, other: Self) -> Self {
        let mut result = self / other;
        if self.x % other.x != 0 {
            result.x -= 1;
        }
        if self.y % other.y != 0 {
            result.y -= 1;
        }
        result
    }
}

pub trait ChunkIndex {
    fn chunk_file_name(self) -> String;
}

impl ChunkIndex for IVec2 {
    fn chunk_file_name(self) -> String {
        format!("{}_{}", self.x, self.y)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_v2u() {
        assert_eq!(Vec2::new(1.5, 1.5).round_to_uvec(), UVec2::new(2, 2));
        assert_eq!(Vec2::new(1.4, 1.4).round_to_uvec(), UVec2::new(1, 1));
        assert_eq!(Vec2::new(1.6, 1.6).round_to_uvec(), UVec2::new(2, 2));
        assert_eq!(Vec2::new(1.9, 1.9).round_to_uvec(), UVec2::new(2, 2));

        assert_eq!(Vec2::new(1.5, 1.5).ceil_to_uvec(), UVec2::new(2, 2));
        assert_eq!(Vec2::new(1.4, 1.4).ceil_to_uvec(), UVec2::new(2, 2));
        assert_eq!(Vec2::new(1.6, 1.6).ceil_to_uvec(), UVec2::new(2, 2));
        assert_eq!(Vec2::new(1.9, 1.9).ceil_to_uvec(), UVec2::new(2, 2));

        println!("passed");
    }
}