use crate::{
chunk::{mesh::ChunkMesh, Chunk, LayerKind, RawTile},
event::TilemapChunkEvent,
lib::*,
prelude::GridTopology,
tile::Tile,
};
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum ErrorKind {
DimensionError(DimensionError),
LayerExists(usize),
LayerDoesNotExist(usize),
MissingTextureAtlas,
MissingTextureDimensions,
MissingChunk,
ChunkAlreadyExists(Point2),
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
use ErrorKind::*;
match self {
DimensionError(err) => ::std::fmt::Debug::fmt(&err, f),
LayerExists(n) => write!(
f,
"layer {} already exists, try `remove_layer` or `move_layer` first",
n
),
LayerDoesNotExist(n) => write!(f, "layer {} does not exist, try `add_layer` first", n),
MissingTextureAtlas => write!(
f,
"texture atlas is missing, must use `TilemapBuilder::texture_atlas`"
),
MissingTextureDimensions => {
write!(f, "tile dimensions are missing, it is required to set it")
}
MissingChunk => write!(f, "the chunk does not exist, try `add_chunk` first"),
ChunkAlreadyExists(p) => write!(
f,
"the chunk {} already exists, if this was intentional run `remove_chunk` first",
p
),
}
}
}
impl Error for ErrorKind {}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct TilemapError(pub Box<ErrorKind>);
impl Display for TilemapError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(&self.0, f)
}
}
impl Error for TilemapError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.0)
}
}
impl From<ErrorKind> for TilemapError {
fn from(kind: ErrorKind) -> TilemapError {
TilemapError(Box::new(kind))
}
}
impl From<DimensionError> for TilemapError {
fn from(err: DimensionError) -> TilemapError {
TilemapError(Box::new(ErrorKind::DimensionError(err)))
}
}
pub type TilemapResult<T> = Result<T, TilemapError>;
bitflags! {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
struct AutoFlags: u16 {
const NONE = 0b0;
const AUTO_CONFIGURE = 0b0000_0000_0000_0001;
const AUTO_CHUNK = 0b0000_0000_0000_0010;
const AUTO_SPAWN = 0b0000_0000_0000_0100;
}
}
const DEFAULT_TEXTURE_DIMENSIONS: Dimension2 = Dimension2::new(32, 32);
const DEFAULT_CHUNK_DIMENSIONS: Dimension3 = Dimension3::new(32, 32, 1);
const DEFAULT_TILE_SCALE: (f32, f32, f32) = (1.0, 1.0, 1.0);
const DEFAULT_Z_LAYERS: usize = 5;
impl Default for AutoFlags {
fn default() -> Self {
AutoFlags::AUTO_CONFIGURE & AutoFlags::AUTO_CHUNK
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct TilemapLayer {
pub kind: LayerKind,
}
impl Default for TilemapLayer {
fn default() -> TilemapLayer {
TilemapLayer {
kind: LayerKind::Dense,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Tilemap {
topology: GridTopology,
dimensions: Option<Dimension2>,
chunk_dimensions: Dimension3,
layer_offset: Vec2,
chunk_mesh: ChunkMesh,
texture_dimensions: Dimension2,
layers: Vec<Option<TilemapLayer>>,
auto_flags: AutoFlags,
auto_spawn: Option<Dimension2>,
custom_flags: Vec<u32>,
#[cfg_attr(feature = "serde", serde(skip))]
texture_atlas: Handle<TextureAtlas>,
chunks: HashMap<Point2, Chunk>,
#[cfg_attr(feature = "serde", serde(skip))]
entities: HashMap<usize, Vec<Entity>>,
#[cfg_attr(feature = "serde", serde(skip))]
chunk_events: Events<TilemapChunkEvent>,
spawned: HashSet<(i32, i32)>,
}
#[derive(Clone, PartialEq, Debug)]
pub struct TilemapBuilder {
topology: GridTopology,
dimensions: Option<Dimension2>,
chunk_dimensions: Dimension3,
layer_offset: Vec2,
texture_dimensions: Option<Dimension2>,
tile_scale: Vec3,
z_layers: usize,
layers: Option<HashMap<usize, TilemapLayer>>,
texture_atlas: Option<Handle<TextureAtlas>>,
render_depth: usize,
auto_flags: AutoFlags,
auto_spawn: Option<Dimension2>,
}
impl Default for TilemapBuilder {
fn default() -> Self {
let layers = {
let mut map = HashMap::default();
map.insert(
0,
TilemapLayer {
kind: LayerKind::Dense,
},
);
Some(map)
};
TilemapBuilder {
topology: GridTopology::Square,
dimensions: None,
chunk_dimensions: DEFAULT_CHUNK_DIMENSIONS,
layer_offset: Vec2::new(0., 0.),
texture_dimensions: None,
tile_scale: DEFAULT_TILE_SCALE.into(),
z_layers: DEFAULT_Z_LAYERS,
layers,
texture_atlas: None,
render_depth: 0,
auto_flags: AutoFlags::NONE,
auto_spawn: None,
}
}
}
impl TilemapBuilder {
pub fn new() -> TilemapBuilder {
TilemapBuilder::default()
}
pub fn topology(mut self, topology: GridTopology) -> TilemapBuilder {
self.topology = topology;
self
}
pub fn dimensions(mut self, width: u32, height: u32) -> TilemapBuilder {
self.dimensions = Some(Dimension2::new(width, height));
self
}
pub fn chunk_dimensions(mut self, width: u32, height: u32, depth: u32) -> TilemapBuilder {
self.chunk_dimensions = Dimension3::new(width, height, depth);
self
}
pub fn layer_offset(mut self, offset: Vec2) -> TilemapBuilder {
self.layer_offset = offset;
self
}
pub fn texture_dimensions(mut self, width: u32, height: u32) -> TilemapBuilder {
self.texture_dimensions = Some(Dimension2::new(width, height));
self
}
pub fn tile_scale(mut self, width: f32, height: f32, depth: f32) -> TilemapBuilder {
self.tile_scale = Vec3::new(width, height, depth);
self
}
pub fn z_layers(mut self, layers: usize) -> TilemapBuilder {
self.z_layers = layers;
self
}
pub fn add_layer(mut self, layer: TilemapLayer, sprite_order: usize) -> TilemapBuilder {
if let Some(layers) = &mut self.layers {
layers.insert(sprite_order, layer);
} else {
let mut layers = HashMap::default();
layers.insert(sprite_order, layer);
self.layers = Some(layers);
}
self
}
pub fn texture_atlas(mut self, handle: Handle<TextureAtlas>) -> TilemapBuilder {
self.texture_atlas = Some(handle);
self
}
pub fn auto_chunk(mut self) -> Self {
self.auto_flags.toggle(AutoFlags::AUTO_CHUNK);
self
}
pub fn auto_spawn(mut self, width: u32, height: u32) -> Self {
self.auto_spawn = Some(Dimension2::new(width, height));
self
}
pub fn finish(self) -> TilemapResult<Tilemap> {
let texture_atlas = if let Some(atlas) = self.texture_atlas {
atlas
} else {
return Err(ErrorKind::MissingTextureAtlas.into());
};
let texture_dimensions = if let Some(dimensions) = self.texture_dimensions {
dimensions
} else {
return Err(ErrorKind::MissingTextureDimensions.into());
};
let z_layers = if let Some(layers) = &self.layers {
if self.z_layers > layers.len() {
self.z_layers
} else {
layers.len()
}
} else {
self.z_layers
};
let layer_count = if let Some(layers) = &self.layers {
layers.iter().count()
} else {
0
};
let chunk_mesh =
ChunkMesh::new(self.chunk_dimensions, layer_count as u32, self.layer_offset);
let layers = {
let mut layers = vec![None; z_layers];
if let Some(map_layers) = self.layers {
for (index, layer) in map_layers {
if let Some(l) = layers.get_mut(index) {
*l = Some(layer)
}
}
}
layers
};
Ok(Tilemap {
topology: self.topology,
dimensions: self.dimensions,
chunk_dimensions: self.chunk_dimensions,
layer_offset: self.layer_offset,
chunk_mesh,
texture_dimensions,
layers,
auto_flags: self.auto_flags,
auto_spawn: self.auto_spawn,
custom_flags: Vec::new(),
texture_atlas,
chunks: Default::default(),
entities: Default::default(),
chunk_events: Default::default(),
spawned: Default::default(),
})
}
}
impl TypeUuid for Tilemap {
const TYPE_UUID: Uuid = Uuid::from_u128(109481186966523254410691740507722642628);
}
impl Default for Tilemap {
fn default() -> Self {
Tilemap {
topology: GridTopology::Square,
dimensions: None,
chunk_dimensions: DEFAULT_CHUNK_DIMENSIONS,
layer_offset: Vec2::default(),
chunk_mesh: ChunkMesh::default(),
texture_dimensions: DEFAULT_TEXTURE_DIMENSIONS,
layers: vec![
Some(TilemapLayer {
kind: LayerKind::Sparse,
}),
None,
None,
None,
None,
],
auto_flags: AutoFlags::NONE,
auto_spawn: None,
custom_flags: Vec::new(),
texture_atlas: Handle::default(),
chunks: Default::default(),
entities: Default::default(),
chunk_events: Default::default(),
spawned: Default::default(),
}
}
}
impl Tilemap {
pub fn new(
texture_atlas: Handle<TextureAtlas>,
texture_width: u32,
texture_height: u32,
) -> Tilemap {
Tilemap {
texture_atlas,
texture_dimensions: Dimension2::new(texture_width, texture_height),
..Default::default()
}
}
pub fn builder() -> TilemapBuilder {
TilemapBuilder::default()
}
pub fn set_texture_atlas(&mut self, handle: Handle<TextureAtlas>) {
self.texture_atlas = handle;
}
pub fn texture_atlas(&self) -> &Handle<TextureAtlas> {
&self.texture_atlas
}
pub fn insert_chunk<P: Into<Point2>>(&mut self, point: P) -> TilemapResult<()> {
let point: Point2 = point.into();
if let Some(dimensions) = &self.dimensions {
dimensions.check_point(point)?;
}
let layer_kinds = self
.layers
.iter()
.map(|x| x.and_then(|y| Some(y.kind)))
.collect::<Vec<Option<LayerKind>>>();
let chunk = Chunk::new(point, &layer_kinds, self.chunk_dimensions);
match self.chunks.insert(point, chunk) {
Some(_) => Err(ErrorKind::ChunkAlreadyExists(point).into()),
None => Ok(()),
}
}
pub fn contains_chunk<P: Into<Point2>>(&mut self, point: P) -> bool {
let point: Point2 = point.into();
self.chunks.contains_key(&point)
}
#[deprecated(
since = "0.4.0",
note = "Please use `add_layer` method instead with the `TilemapLayer` struct"
)]
#[doc(hidden)]
pub fn add_layer_with_kind(
&mut self,
kind: LayerKind,
sprite_order: usize,
) -> TilemapResult<()> {
let layer = TilemapLayer { kind };
if let Some(some_kind) = self.layers.get_mut(sprite_order) {
if some_kind.is_some() {
return Err(ErrorKind::LayerExists(sprite_order).into());
}
*some_kind = Some(layer);
}
for chunk in self.chunks.values_mut() {
chunk.add_sprite_layer(&kind, sprite_order, self.chunk_dimensions);
}
Ok(())
}
pub fn add_layer(&mut self, layer: TilemapLayer, sprite_layer: usize) -> TilemapResult<()> {
if let Some(inner_layer) = self.layers.get_mut(sprite_layer) {
if inner_layer.is_some() {
return Err(ErrorKind::LayerExists(sprite_layer).into());
}
*inner_layer = Some(layer);
}
let mut layers = 0;
for layer in self.layers().iter() {
if layer.is_some() {
layers += 1;
}
}
let chunk_mesh = ChunkMesh::new(self.chunk_dimensions, layers, self.layer_offset);
self.chunk_mesh = chunk_mesh;
self.chunk_events.send(TilemapChunkEvent::AddLayer {
layer_kind: layer.kind,
sprite_layer,
});
Ok(())
}
pub fn move_layer(
&mut self,
from_sprite_order: usize,
to_sprite_order: usize,
) -> TilemapResult<()> {
if let Some(layer) = self.layers.get(to_sprite_order) {
if layer.is_some() {
return Err(ErrorKind::LayerExists(to_sprite_order).into());
}
};
if let Some(layer) = self.layers.get(from_sprite_order) {
if Some(layer).is_none() {
return Err(ErrorKind::LayerDoesNotExist(from_sprite_order).into());
}
}
self.layers.swap(from_sprite_order, to_sprite_order);
for chunk in self.chunks.values_mut() {
chunk.move_sprite_layer(from_sprite_order, to_sprite_order);
}
Ok(())
}
pub fn remove_layer(&mut self, z: usize) {
if let Some(layer) = self.layers.get_mut(z) {
*layer = None;
} else {
return;
}
for chunk in self.chunks.values_mut() {
chunk.remove_sprite_layer(z);
}
}
pub fn spawn_chunk<P: Into<Point2>>(&mut self, point: P) -> TilemapResult<()> {
let point: Point2 = point.into();
if let Some(dimensions) = &self.dimensions {
dimensions.check_point(point)?;
}
if self.spawned.contains(&(point.x, point.y)) {
return Ok(());
} else {
self.chunk_events.send(TilemapChunkEvent::Spawned { point });
}
Ok(())
}
pub fn spawn_chunk_containing_point<P: Into<Point2>>(&mut self, point: P) -> TilemapResult<()> {
let point = self.point_to_chunk_point(point);
self.spawn_chunk(point)
}
pub fn despawn_chunk<P: Into<Point2>>(&mut self, point: P) -> TilemapResult<()> {
let point: Point2 = point.into();
if let Some(dimensions) = &self.dimensions {
dimensions.check_point(point)?;
}
self.spawned.remove(&(point.x, point.y));
if self.chunks.get_mut(&point).is_some() {
self.chunk_events
.send(TilemapChunkEvent::Despawned { point });
Ok(())
} else {
Err(ErrorKind::MissingChunk.into())
}
}
pub fn remove_chunk<P: Into<Point2>>(&mut self, point: P) -> TilemapResult<()> {
let point = point.into();
self.despawn_chunk(point)?;
self.chunks.remove(&point);
Ok(())
}
pub fn point_to_chunk_point<P: Into<Point2>>(&self, point: P) -> (i32, i32) {
let point: Point2 = point.into();
let width = self.chunk_dimensions.width as f32;
let height = self.chunk_dimensions.height as f32;
let x = ((point.x as f32 + width / 2.0) / width).floor() as i32;
let y = ((point.y as f32 + height / 2.0) / height).floor() as i32;
(x, y)
}
fn sort_tiles_to_chunks<P, I>(
&mut self,
tiles: I,
) -> TilemapResult<HashMap<Point2, Vec<Tile<Point3>>>>
where
P: Into<Point3>,
I: IntoIterator<Item = Tile<P>>,
{
let width = self.chunk_dimensions.width as i32;
let height = self.chunk_dimensions.height as i32;
let mut chunk_map: HashMap<Point2, Vec<Tile<Point3>>> = HashMap::default();
for tile in tiles.into_iter() {
let global_tile_point: Point3 = tile.point.into();
let chunk_point: Point2 = self.point_to_chunk_point(global_tile_point).into();
if let Some(layer) = self.layers.get(tile.sprite_order as usize) {
if layer.as_ref().is_none() {
self.add_layer(TilemapLayer::default(), tile.sprite_order as usize)?;
}
} else {
return Err(ErrorKind::LayerDoesNotExist(tile.sprite_order).into());
}
let tile_point = Point3::new(
global_tile_point.x - (width * chunk_point.x) + (width / 2),
global_tile_point.y - (height * chunk_point.y) + (height / 2),
global_tile_point.z,
);
let chunk_tile: Tile<Point3> = Tile {
point: tile_point,
sprite_order: tile.sprite_order,
sprite_index: tile.sprite_index,
tint: tile.tint,
};
if let Some(tiles) = chunk_map.get_mut(&chunk_point) {
tiles.push(chunk_tile);
} else {
let tiles = vec![chunk_tile];
chunk_map.insert(chunk_point, tiles);
}
}
Ok(chunk_map)
}
pub fn insert_tiles<P, I>(&mut self, tiles: I) -> TilemapResult<()>
where
P: Into<Point3>,
I: IntoIterator<Item = Tile<P>>,
{
let chunk_map = self.sort_tiles_to_chunks(tiles)?;
for (chunk_point, tiles) in chunk_map.into_iter() {
let layers = self.layers.clone();
let chunk_dimensions = self.chunk_dimensions;
let chunk = if self.auto_flags.contains(AutoFlags::AUTO_CHUNK) {
self.chunks.entry(chunk_point).or_insert_with(|| {
let layer_kinds = layers
.iter()
.map(|x| x.and_then(|y| Some(y.kind)))
.collect::<Vec<Option<LayerKind>>>();
Chunk::new(chunk_point, &layer_kinds, chunk_dimensions)
})
} else {
match self.chunks.get_mut(&chunk_point) {
Some(c) => c,
None => return Err(ErrorKind::MissingChunk.into()),
}
};
for tile in tiles.iter() {
let index = self.chunk_dimensions.encode_point_unchecked(tile.point);
chunk.set_tile(index, *tile);
}
if chunk.mesh().is_some() {
self.chunk_events.send(TilemapChunkEvent::Modified {
point: chunk.point(),
});
}
}
Ok(())
}
pub fn insert_tile<P: Into<Point3>>(&mut self, tile: Tile<P>) -> TilemapResult<()> {
let tiles = vec![tile];
self.insert_tiles(tiles)
}
pub fn clear_tiles<P, I>(&mut self, points: I) -> TilemapResult<()>
where
P: Into<Point3>,
I: IntoIterator<Item = (P, usize)>,
{
let mut tiles = Vec::new();
for (point, sprite_order) in points {
tiles.push(Tile {
point: point.into(),
sprite_index: 0,
sprite_order,
tint: Color::rgba(0.0, 0.0, 0.0, 0.0),
});
}
let chunk_map = self.sort_tiles_to_chunks(tiles)?;
for (chunk_point, tiles) in chunk_map.into_iter() {
let chunk = match self.chunks.get_mut(&chunk_point) {
Some(c) => c,
None => return Err(ErrorKind::MissingChunk.into()),
};
for tile in tiles.iter() {
let index = self.chunk_dimensions.encode_point_unchecked(tile.point);
chunk.remove_tile(index, tile.sprite_order, tile.point.z as usize);
}
self.chunk_events.send(TilemapChunkEvent::Modified {
point: chunk.point(),
});
}
Ok(())
}
fn point_to_tile_point(&self, point: Point3) -> Point3 {
let chunk_point: Point2 = self.point_to_chunk_point(point).into();
let width = self.chunk_dimensions.width as i32;
let height = self.chunk_dimensions.height as i32;
Point3::new(
point.x - (width * chunk_point.x) + (width / 2),
point.y - (height * chunk_point.y) + (height / 2),
point.z,
)
}
pub fn clear_tile<P>(&mut self, point: P, sprite_order: usize) -> TilemapResult<()>
where
P: Into<Point3>,
{
let points = vec![(point, sprite_order)];
self.clear_tiles(points)
}
pub fn get_tile<P>(&mut self, point: P, sprite_order: usize) -> Option<&RawTile>
where
P: Into<Point3>,
{
let point: Point3 = point.into();
let chunk_point: Point2 = self.point_to_chunk_point(point).into();
let tile_point = self.point_to_tile_point(point);
let chunk = self.chunks.get(&chunk_point)?;
let index = self.chunk_dimensions.encode_point_unchecked(tile_point);
chunk.get_tile(index, sprite_order, point.z as usize)
}
pub fn get_tile_mut<P>(&mut self, point: P, sprite_order: usize) -> Option<&mut RawTile>
where
P: Into<Point3>,
{
let point: Point3 = point.into();
let chunk_point: Point2 = self.point_to_chunk_point(point).into();
let tile_point = self.point_to_tile_point(point);
let chunk = self.chunks.get_mut(&chunk_point)?;
let index = self.chunk_dimensions.encode_point_unchecked(tile_point);
let mut layers = HashMap::default();
layers.insert(sprite_order, chunk_point);
self.chunk_events.send(TilemapChunkEvent::Modified {
point: chunk.point(),
});
chunk.get_tile_mut(index, sprite_order, point.z as usize)
}
pub fn clear_layer(&mut self, layer: usize) -> Result<(), TilemapError> {
if let Some(l) = self.layers.get(layer) {
if l.is_none() {
return Err(ErrorKind::LayerDoesNotExist(layer).into());
}
} else {
return Err(ErrorKind::LayerDoesNotExist(layer).into());
}
for chunk in self.chunks.values_mut() {
chunk.clear_layer(layer);
}
Ok(())
}
pub fn center_tile_coord(&self) -> Option<(i32, i32)> {
self.dimensions.map(|dimensions| {
(
(dimensions.width / 2 * self.chunk_dimensions.width) as i32,
(dimensions.height / 2 * self.chunk_dimensions.height) as i32,
)
})
}
pub fn width(&self) -> Option<u32> {
self.dimensions.map(|dimensions| dimensions.width)
}
pub fn height(&self) -> Option<u32> {
self.dimensions.map(|dimensions| dimensions.height)
}
pub fn chunk_width(&self) -> u32 {
self.chunk_dimensions.width
}
pub fn chunk_height(&self) -> u32 {
self.chunk_dimensions.height
}
pub fn tile_width(&self) -> u32 {
self.texture_dimensions.width
}
pub fn tile_height(&self) -> u32 {
self.texture_dimensions.height
}
pub(crate) fn get_chunk(&self, point: &Point2) -> Option<&Chunk> {
self.chunks.get(point)
}
pub fn topology(&self) -> GridTopology {
self.topology
}
pub fn chunk_events(&self) -> &Events<TilemapChunkEvent> {
&self.chunk_events
}
pub(crate) fn chunk_events_update(&mut self) {
self.chunk_events.update()
}
pub(crate) fn auto_spawn(&self) -> Option<Dimension2> {
self.auto_spawn
}
pub(crate) fn set_auto_spawn(&mut self, dimension: Dimension2) {
self.auto_spawn = Some(dimension);
}
pub(crate) fn chunk_dimensions(&self) -> Dimension3 {
self.chunk_dimensions
}
pub(crate) fn texture_dimensions(&self) -> Dimension2 {
self.texture_dimensions
}
pub(crate) fn spawned_chunks(&self) -> &HashSet<(i32, i32)> {
&self.spawned
}
pub(crate) fn spawned_chunks_mut(&mut self) -> &mut HashSet<(i32, i32)> {
&mut self.spawned
}
pub(crate) fn layers(&self) -> Vec<Option<TilemapLayer>> {
self.layers.clone()
}
pub(crate) fn chunks(&self) -> &HashMap<Point2, Chunk> {
&self.chunks
}
pub(crate) fn chunks_mut(&mut self) -> &mut HashMap<Point2, Chunk> {
&mut self.chunks
}
pub(crate) fn chunk_mesh(&self) -> &ChunkMesh {
&self.chunk_mesh
}
}
#[cfg(test)]
mod tests {
use super::*;
impl Tilemap {
pub(crate) fn modify_chunk(&mut self, point: Point2) {
self.chunk_events
.send(TilemapChunkEvent::Modified { point });
}
}
}