use crate::{
asset::{
io::ResourceIo,
loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
manager::ResourceManager,
state::LoadError,
Resource, ResourceData,
},
core::{
algebra::{Matrix4, Vector2, Vector3},
color::Color,
io::FileLoadError,
reflect::prelude::*,
type_traits::prelude::*,
visitor::prelude::*,
},
scene::debug::SceneDrawingContext,
};
use std::{
error::Error,
fmt::{Display, Formatter},
path::{Path, PathBuf},
sync::Arc,
};
use super::*;
#[derive(Debug)]
pub enum TileMapBrushResourceError {
Io(FileLoadError),
Visit(VisitError),
}
impl Display for TileMapBrushResourceError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TileMapBrushResourceError::Io(v) => {
write!(f, "A file load error has occurred {v:?}")
}
TileMapBrushResourceError::Visit(v) => {
write!(
f,
"An error that may occur due to version incompatibilities. {v:?}"
)
}
}
}
}
impl From<FileLoadError> for TileMapBrushResourceError {
fn from(e: FileLoadError) -> Self {
Self::Io(e)
}
}
impl From<VisitError> for TileMapBrushResourceError {
fn from(e: VisitError) -> Self {
Self::Visit(e)
}
}
#[derive(Default, Debug, Clone, Visit, Reflect)]
pub struct TileMapBrushPage {
pub icon: TileDefinitionHandle,
#[reflect(hidden)]
pub tiles: Tiles,
}
impl TileSource for TileMapBrushPage {
fn transformation(&self) -> OrthoTransformation {
OrthoTransformation::default()
}
fn get_at(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
self.tiles.get(&position).copied()
}
}
impl TileMapBrushPage {
pub fn bounding_rect(&self) -> OptionTileRect {
let mut result = OptionTileRect::default();
for pos in self.tiles.keys() {
result.push(*pos);
}
result
}
pub fn find_tile_at_position(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
self.tiles.get(&position).copied()
}
pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(&self, iter: I, tiles: &mut Tiles) {
for pos in iter {
if let Some(tile) = self.tiles.get(&pos).copied() {
tiles.insert(pos, tile);
}
}
}
pub fn draw_outline(
&self,
ctx: &mut SceneDrawingContext,
position: Vector2<i32>,
world_transform: &Matrix4<f32>,
color: Color,
) {
for (pos, _) in self.tiles.iter() {
draw_tile_outline(ctx, position + pos, world_transform, color);
}
}
}
fn draw_tile_outline(
ctx: &mut SceneDrawingContext,
position: Vector2<i32>,
world_transform: &Matrix4<f32>,
color: Color,
) {
ctx.draw_rectangle(
0.5,
0.5,
Matrix4::new_translation(
&(position.cast::<f32>().to_homogeneous() + Vector3::new(0.5, 0.5, 0.0)),
) * world_transform,
color,
);
}
#[derive(Default, Debug, Clone, Visit, Reflect, TypeUuidProvider)]
#[type_uuid(id = "23ed39da-cb01-4181-a058-94dc77ecb4b2")]
pub struct TileMapBrush {
pub tile_set: Option<TileSetResource>,
#[reflect(hidden)]
pub pages: TileGridMap<TileMapBrushPage>,
#[reflect(hidden)]
#[visit(skip)]
pub change_count: ChangeFlag,
}
impl TileMapBrush {
pub fn has_tile_at(&self, page: Vector2<i32>, tile: Vector2<i32>) -> bool {
let Some(page) = self.pages.get(&page) else {
return false;
};
page.tiles.contains_key(&tile)
}
pub fn has_page_at(&self, page: Vector2<i32>) -> bool {
self.pages.contains_key(&page)
}
pub fn tile_redirect(&self, handle: TileDefinitionHandle) -> Option<TileDefinitionHandle> {
self.find_tile_at_position(TilePaletteStage::Tiles, handle.page(), handle.tile())
}
#[inline]
pub fn pages_bounds(&self) -> OptionTileRect {
let mut result = OptionTileRect::default();
for pos in self.pages.keys() {
result.push(*pos);
}
result
}
pub fn page_icon(&self, page: Vector2<i32>) -> Option<TileDefinitionHandle> {
self.pages.get(&page).map(|p| p.icon)
}
pub fn tiles_bounds(&self, stage: TilePaletteStage, page: Vector2<i32>) -> OptionTileRect {
match stage {
TilePaletteStage::Tiles => {
let Some(page) = self.pages.get(&page) else {
return OptionTileRect::default();
};
page.bounding_rect()
}
TilePaletteStage::Pages => self.pages_bounds(),
}
}
pub fn find_tile_at_position(
&self,
stage: TilePaletteStage,
page: Vector2<i32>,
position: Vector2<i32>,
) -> Option<TileDefinitionHandle> {
match stage {
TilePaletteStage::Pages => self.pages.get(&position).map(|p| p.icon),
TilePaletteStage::Tiles => self
.pages
.get(&page)
.and_then(|p| p.find_tile_at_position(position)),
}
}
pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(
&self,
stage: TilePaletteStage,
page: Vector2<i32>,
iter: I,
tiles: &mut Tiles,
) {
match stage {
TilePaletteStage::Pages => {
for pos in iter {
if let Some(handle) = self.pages.get(&pos).map(|p| p.icon) {
tiles.insert(pos, handle);
}
}
}
TilePaletteStage::Tiles => {
if let Some(page) = self.pages.get(&page) {
page.get_tiles(iter, tiles);
}
}
}
}
pub fn is_missing_tile_set(&self) -> bool {
self.tile_set.is_none()
}
fn palette_render_loop_without_tile_set<F>(
&self,
stage: TilePaletteStage,
page: Vector2<i32>,
mut func: F,
) where
F: FnMut(Vector2<i32>, TileRenderData),
{
match stage {
TilePaletteStage::Pages => {
for k in self.pages.keys() {
func(*k, TileRenderData::missing_data());
}
}
TilePaletteStage::Tiles => {
let Some(page) = self.pages.get(&page) else {
return;
};
for k in page.tiles.keys() {
func(*k, TileRenderData::missing_data());
}
}
}
}
pub fn palette_render_loop<F>(&self, stage: TilePaletteStage, page: Vector2<i32>, mut func: F)
where
F: FnMut(Vector2<i32>, TileRenderData),
{
let Some(tile_set) = self.tile_set.as_ref() else {
self.palette_render_loop_without_tile_set(stage, page, func);
return;
};
let mut state = tile_set.state();
let Some(tile_set) = state.data() else {
self.palette_render_loop_without_tile_set(stage, page, func);
return;
};
match stage {
TilePaletteStage::Pages => {
for (k, p) in self.pages.iter() {
let data = tile_set
.get_tile_render_data(p.icon.into())
.unwrap_or_else(TileRenderData::missing_data);
func(*k, data);
}
}
TilePaletteStage::Tiles => {
let Some(page) = self.pages.get(&page) else {
return;
};
for (k, &handle) in page.tiles.iter() {
let data = tile_set
.get_tile_render_data(handle.into())
.unwrap_or_else(TileRenderData::missing_data);
func(*k, data);
}
}
}
}
pub fn get_tile_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
let handle = self.redirect_handle(position)?;
let mut tile_set = self.tile_set.as_ref()?.state();
let data = tile_set
.data()?
.get_tile_render_data(handle.into())
.unwrap_or_else(TileRenderData::missing_data);
Some(data)
}
pub fn redirect_handle(&self, position: ResourceTilePosition) -> Option<TileDefinitionHandle> {
match position.stage() {
TilePaletteStage::Tiles => {
let page = self.pages.get(&position.page())?;
page.tiles.get_at(position.stage_position())
}
TilePaletteStage::Pages => self
.pages
.get(&position.stage_position())
.map(|page| page.icon),
}
}
pub fn get_tile_bounds(&self, position: ResourceTilePosition) -> Option<TileMaterialBounds> {
let handle = self.redirect_handle(position)?;
self.tile_set
.as_ref()?
.state()
.data()?
.get_tile_bounds(handle.into())
}
pub async fn from_file(
path: &Path,
resource_manager: ResourceManager,
io: &dyn ResourceIo,
) -> Result<Self, TileMapBrushResourceError> {
let bytes = io.load_file(path).await?;
let mut visitor = Visitor::load_from_memory(&bytes)?;
visitor.blackboard.register(Arc::new(resource_manager));
let mut tile_map_brush = Self::default();
tile_map_brush.visit("TileMapBrush", &mut visitor)?;
Ok(tile_map_brush)
}
fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
let mut visitor = Visitor::new();
self.visit("TileMapBrush", &mut visitor)?;
visitor.save_binary(path)?;
Ok(())
}
}
impl ResourceData for TileMapBrush {
fn type_uuid(&self) -> Uuid {
<Self as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
self.save(path)
}
fn can_be_saved(&self) -> bool {
true
}
}
pub struct TileMapBrushLoader {
pub resource_manager: ResourceManager,
}
impl ResourceLoader for TileMapBrushLoader {
fn extensions(&self) -> &[&str] {
&["tile_map_brush"]
}
fn data_type_uuid(&self) -> Uuid {
<TileMapBrush as TypeUuidProvider>::type_uuid()
}
fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
let resource_manager = self.resource_manager.clone();
Box::pin(async move {
let tile_map_brush = TileMapBrush::from_file(&path, resource_manager, io.as_ref())
.await
.map_err(LoadError::new)?;
Ok(LoaderPayload::new(tile_map_brush))
})
}
}
pub type TileMapBrushResource = Resource<TileMapBrush>;