#![allow(unused_variables)]
use crate::{CoordinateEncoder, TileOutOfBoundsError};
use amethyst_assets::{Asset, Handle};
use amethyst_core::{
ecs::{Component, HashMapStorage, World},
math::{Matrix4, Point3, Vector3},
Transform,
};
use amethyst_rendy::{palette::Srgba, SpriteSheet};
pub trait Tile: 'static + Clone + Send + Sync + Default {
fn sprite(&self, coordinates: Point3<u32>, world: &World) -> Option<usize> {
None
}
fn tint(&self, coordinates: Point3<u32>, world: &World) -> Srgba {
Srgba::new(1.0, 1.0, 1.0, 1.0)
}
}
pub trait Map {
fn tile_dimensions(&self) -> &Vector3<u32>;
fn dimensions(&self) -> &Vector3<u32>;
fn origin(&self) -> &Point3<f32>;
fn set_sprite_sheet(&mut self, sprite_sheet: Option<Handle<SpriteSheet>>);
fn to_world(&self, coord: &Point3<u32>, map_transform: Option<&Transform>) -> Vector3<f32>;
fn to_tile(
&self,
coord: &Vector3<f32>,
map_transform: Option<&Transform>,
) -> Result<Point3<u32>, TileOutOfBoundsError>;
fn transform(&self) -> &Matrix4<f32>;
fn encode(&self, coord: &Point3<u32>) -> Option<u32>;
fn encode_raw(&self, coord: &(u32, u32, u32)) -> Option<u32>;
fn decode(&self, morton: u32) -> Option<Point3<u32>>;
fn decode_raw(&self, morton: u32) -> Option<(u32, u32, u32)>;
}
pub trait MapStorage<T: Tile> {
fn get(&self, coord: &Point3<u32>) -> Option<&T>;
fn get_mut(&mut self, coord: &Point3<u32>) -> Option<&mut T>;
fn get_mut_nochange(&mut self, coord: &Point3<u32>) -> Option<&mut T>;
fn get_raw(&self, coord: u32) -> Option<&T>;
fn get_raw_mut(&mut self, coord: u32) -> Option<&mut T>;
fn get_raw_mut_nochange(&mut self, coord: u32) -> Option<&mut T>;
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TileMap<T: Tile, E: CoordinateEncoder = crate::MortonEncoder2D> {
pub(crate) origin: Point3<f32>,
pub(crate) tile_dimensions: Vector3<u32>,
pub(crate) dimensions: Vector3<u32>,
pub(crate) transform: Matrix4<f32>,
pub(crate) version: u64,
#[serde(skip)]
pub(crate) sprite_sheet: Option<Handle<SpriteSheet>>,
pub(crate) data: Vec<T>,
#[serde(skip)]
pub(crate) encoder: E,
}
impl<T: Tile, E: CoordinateEncoder> Asset for TileMap<T, E> {
const NAME: &'static str = "tiles::map";
type Data = Self;
type HandleStorage = HashMapStorage<Handle<Self>>;
}
impl<T: Tile, E: CoordinateEncoder> Component for TileMap<T, E> {
type Storage = HashMapStorage<Self>;
}
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
impl<T: Tile, E: CoordinateEncoder> TileMap<T, E> {
pub fn version(&self) -> u64 {
self.version
}
pub fn new(
dimensions: Vector3<u32>,
tile_dimensions: Vector3<u32>,
sprite_sheet: Option<Handle<SpriteSheet>>,
) -> Self {
let origin = Point3::new(0.0, 0.0, 0.0);
let transform = create_transform(&dimensions, &tile_dimensions);
let size = E::allocation_size(dimensions);
let mut data = Vec::with_capacity(size);
data.resize_with(size, T::default);
let encoder = E::from_dimensions(dimensions);
Self {
data,
origin,
dimensions,
tile_dimensions,
sprite_sheet,
transform,
encoder,
version: 1,
}
}
}
impl<T: Tile, E: CoordinateEncoder> Map for TileMap<T, E> {
#[inline]
fn tile_dimensions(&self) -> &Vector3<u32> {
&self.tile_dimensions
}
#[inline]
fn origin(&self) -> &Point3<f32> {
&self.origin
}
#[inline]
fn dimensions(&self) -> &Vector3<u32> {
&self.dimensions
}
#[inline]
fn set_sprite_sheet(&mut self, sprite_sheet: Option<Handle<SpriteSheet>>) {
self.sprite_sheet = sprite_sheet;
}
#[inline]
fn to_world(&self, coord: &Point3<u32>, map_transform: Option<&Transform>) -> Vector3<f32> {
to_world(&self.transform, coord, map_transform)
}
#[inline]
#[allow(clippy::let_and_return)]
fn to_tile(
&self,
coord: &Vector3<f32>,
map_transform: Option<&Transform>,
) -> Result<Point3<u32>, TileOutOfBoundsError> {
to_tile(&self.transform, coord, self.dimensions(), map_transform)
}
#[inline]
fn transform(&self) -> &Matrix4<f32> {
&self.transform
}
#[inline]
fn encode(&self, coord: &Point3<u32>) -> Option<u32> {
self.encode_raw(&(coord.x, coord.y, coord.z))
}
#[inline]
fn encode_raw(&self, coord: &(u32, u32, u32)) -> Option<u32> {
self.encoder.encode(coord.0, coord.1, coord.2)
}
#[inline]
fn decode(&self, morton: u32) -> Option<Point3<u32>> {
let coords = self.encoder.decode(morton)?;
Some(Point3::new(coords.0, coords.1, coords.2))
}
#[inline]
fn decode_raw(&self, morton: u32) -> Option<(u32, u32, u32)> {
self.encoder.decode(morton)
}
}
impl<T: Tile, E: CoordinateEncoder> MapStorage<T> for TileMap<T, E> {
#[inline]
fn get(&self, coord: &Point3<u32>) -> Option<&T> {
self.get_raw(self.encode(coord)?)
}
#[inline]
fn get_mut(&mut self, coord: &Point3<u32>) -> Option<&mut T> {
self.get_raw_mut(self.encode(coord)?)
}
#[inline]
fn get_mut_nochange(&mut self, coord: &Point3<u32>) -> Option<&mut T> {
self.get_raw_mut_nochange(self.encode(coord)?)
}
#[inline]
fn get_raw(&self, coord: u32) -> Option<&T> {
#[cfg(debug_assertions)]
{
if coord > self.encode(&Point3::from(*self.dimensions()))? {
return None;
}
}
self.data.get(coord as usize)
}
#[inline]
fn get_raw_mut(&mut self, coord: u32) -> Option<&mut T> {
self.version += 1;
self.data.get_mut(coord as usize)
}
#[inline]
fn get_raw_mut_nochange(&mut self, coord: u32) -> Option<&mut T> {
self.data.get_mut(coord as usize)
}
}
#[allow(clippy::cast_precision_loss)]
fn create_transform(map_dimensions: &Vector3<u32>, tile_dimensions: &Vector3<u32>) -> Matrix4<f32> {
let tile_dimensions = Vector3::new(
tile_dimensions.x as f32,
tile_dimensions.y as f32,
tile_dimensions.z as f32,
);
let half_dimensions = Vector3::new(
-1.0 * (map_dimensions.x as f32 / 2.0),
map_dimensions.y as f32 / 2.0,
0.0,
);
Matrix4::new_translation(&half_dimensions).append_nonuniform_scaling(&tile_dimensions)
}
#[allow(clippy::cast_precision_loss)]
fn to_world(
transform: &Matrix4<f32>,
coord: &Point3<u32>,
map_transform: Option<&Transform>,
) -> Vector3<f32> {
let coord_f = Point3::new(coord.x as f32, -1.0 * coord.y as f32, coord.z as f32);
if let Some(map_trans) = map_transform {
map_trans
.global_matrix()
.transform_point(&transform.transform_point(&coord_f))
.coords
} else {
transform.transform_point(&coord_f).coords
}
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn to_tile(
transform: &Matrix4<f32>,
coord: &Vector3<f32>,
max_dimensions: &Vector3<u32>,
map_transform: Option<&Transform>,
) -> Result<Point3<u32>, TileOutOfBoundsError> {
let point = if let Some(map_trans) = map_transform {
map_trans
.global_view_matrix()
.transform_point(&Point3::from(*coord))
} else {
Point3::from(*coord)
};
let mut inverse = transform
.try_inverse()
.unwrap()
.transform_point(&point)
.coords;
inverse.x = inverse.x.round();
inverse.y = inverse.y.round() * -1.0;
inverse.z = inverse.z.floor();
if inverse.x < 0.0
|| inverse.x as u32 >= max_dimensions.x
|| inverse.y < 0.0
|| inverse.y as u32 >= max_dimensions.y
|| inverse.z < 0.0
|| inverse.z as u32 >= max_dimensions.z
{
let point_dimensions = Point3::new(inverse.x as i32, inverse.y as i32, inverse.z as i32);
Err(TileOutOfBoundsError {
point_dimensions,
max_dimensions: *max_dimensions,
})
} else {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
Ok(Point3::new(
inverse.x as u32,
inverse.y as u32,
inverse.z as u32,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
morton::{MortonEncoder, MortonEncoder2D},
FlatEncoder,
};
use amethyst_core::math::Point3;
use rayon::prelude::*;
#[derive(Clone, Debug)]
struct TestTile {
point: Point3<u32>,
}
impl Default for TestTile {
fn default() -> Self {
Self {
point: Point3::new(0, 0, 0),
}
}
}
impl Tile for TestTile {
fn sprite(&self, _: Point3<u32>, _: &World) -> Option<usize> {
None
}
}
pub fn test_single_map<E: CoordinateEncoder>(dimensions: Vector3<u32>) {
struct UnsafeWrapper<E: CoordinateEncoder> {
ptr: *mut TileMap<TestTile, E>,
}
impl<E: CoordinateEncoder> UnsafeWrapper<E> {
pub fn new(map: &mut TileMap<TestTile, E>) -> Self {
Self {
ptr: map as *mut TileMap<TestTile, E>,
}
}
pub fn get(&self) -> &TileMap<TestTile, E> {
unsafe { &*self.ptr }
}
#[allow(clippy::mut_from_ref)]
pub fn get_mut(&self) -> &mut TileMap<TestTile, E> {
unsafe { &mut *self.ptr }
}
}
unsafe impl<E: CoordinateEncoder> Send for UnsafeWrapper<E> {}
unsafe impl<E: CoordinateEncoder> Sync for UnsafeWrapper<E> {}
let mut inner = TileMap::<TestTile, E>::new(dimensions, Vector3::new(10, 10, 1), None);
let map = UnsafeWrapper::new(&mut inner);
(0..dimensions.x).for_each(|x| {
(0..dimensions.y).for_each(|y| {
for z in 0..dimensions.z {
let point = Point3::new(x, y, z);
*map.get_mut().get_mut(&point).unwrap() = TestTile { point };
}
});
});
(0..dimensions.x).for_each(|x| {
(0..dimensions.y).for_each(|y| {
for z in 0..dimensions.z {
let point = Point3::new(x, y, z);
assert_eq!(map.get().get(&Point3::new(x, y, z)).unwrap().point, point);
}
});
});
}
#[test]
pub fn asymmetric_maps() {
let test_dimensions = [
Vector3::new(10, 58, 54),
Vector3::new(66, 5, 20),
Vector3::new(199, 100, 1),
Vector3::new(5, 55, 6),
Vector3::new(15, 23, 1),
Vector3::new(20, 12, 12),
Vector3::new(48, 48, 12),
Vector3::new(12, 55, 12),
Vector3::new(26, 25, 1),
Vector3::new(1, 2, 5),
];
test_dimensions.into_par_iter().for_each(|dimensions| {
test_single_map::<MortonEncoder>(*dimensions);
test_single_map::<MortonEncoder2D>(*dimensions);
test_single_map::<FlatEncoder>(*dimensions);
});
}
pub fn test_coord(transform: &Matrix4<f32>, tile: Point3<u32>, world: Point3<f32>) {
let world_result = to_world(transform, &tile, None);
assert_eq!(world_result, world.coords);
let tile_result =
to_tile(transform, &world.coords, &Vector3::new(100, 100, 100), None).unwrap();
assert_eq!(tile_result, tile);
let world_reverse =
to_tile(transform, &world_result, &Vector3::new(100, 100, 100), None).unwrap();
assert_eq!(world_reverse, tile);
let tile_reverse = to_world(transform, &tile_result, None);
assert_eq!(tile_reverse, world.coords);
}
#[test]
pub fn tilemap_coord_conversions() {
let transform = create_transform(&Vector3::new(64, 64, 64), &Vector3::new(10, 10, 1));
test_coord(
&transform,
Point3::new(0, 0, 0),
Point3::new(-320.0, 320.0, 0.0),
);
test_coord(
&transform,
Point3::new(1, 0, 0),
Point3::new(-310.0, 320.0, 0.0),
);
test_coord(
&transform,
Point3::new(0, 1, 0),
Point3::new(-320.0, 310.0, 0.0),
);
test_coord(
&transform,
Point3::new(0, 1, 20),
Point3::new(-320.0, 310.0, 20.0),
);
}
pub fn test_coord_with_map_transform(
transform: &Matrix4<f32>,
tile: Point3<u32>,
world: Point3<f32>,
map_transform: &Transform,
) {
let world_result = to_world(transform, &tile, Some(map_transform));
assert_eq!(world_result, world.coords);
let tile_result = to_tile(
transform,
&world.coords,
&Vector3::new(100, 100, 100),
Some(map_transform),
)
.unwrap();
assert_eq!(tile_result, tile);
let world_reverse = to_tile(
transform,
&world_result,
&Vector3::new(100, 100, 100),
Some(map_transform),
)
.unwrap();
assert_eq!(world_reverse, tile);
let tile_reverse = to_world(transform, &tile_result, Some(map_transform));
assert_eq!(tile_reverse, world.coords);
}
#[test]
pub fn tilemap_coord_conversions_with_map_transform() {
let transform = create_transform(&Vector3::new(64, 64, 64), &Vector3::new(10, 10, 1));
let mut map_transform = Transform::default();
map_transform.set_translation_xyz(-10.0, 10.0, 0.0);
map_transform.copy_local_to_global();
test_coord_with_map_transform(
&transform,
Point3::new(1, 1, 0),
Point3::new(-320.0, 320.0, 0.0),
&map_transform,
);
test_coord_with_map_transform(
&transform,
Point3::new(2, 1, 0),
Point3::new(-310.0, 320.0, 0.0),
&map_transform,
);
test_coord_with_map_transform(
&transform,
Point3::new(1, 2, 0),
Point3::new(-320.0, 310.0, 0.0),
&map_transform,
);
test_coord_with_map_transform(
&transform,
Point3::new(1, 2, 20),
Point3::new(-320.0, 310.0, 20.0),
&map_transform,
);
}
}