use fyrox_core::{futures::executor::block_on, log::Log};
use crate::{
asset::{
io::ResourceIo,
loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
manager::ResourceManager,
state::LoadError,
untyped::UntypedResource,
Resource, ResourceData,
},
core::{
algebra::{Matrix4, Vector2, Vector3},
color::Color,
io::FileError,
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(FileError),
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<FileError> for TileMapBrushResourceError {
fn from(e: FileError) -> Self {
Self::Io(e)
}
}
impl From<VisitError> for TileMapBrushResourceError {
fn from(e: VisitError) -> Self {
Self::Visit(e)
}
}
#[derive(Debug, Clone, Default, Reflect)]
pub struct BrushMacroInstanceList(Vec<BrushMacroData>);
impl BrushMacroInstanceList {
pub fn instances_with_uuid(&self, uuid: Uuid) -> impl Iterator<Item = &UntypedResource> {
self.0
.iter()
.filter(move |d| d.macro_id == uuid)
.filter_map(|d| d.settings.as_ref())
}
}
impl Deref for BrushMacroInstanceList {
type Target = Vec<BrushMacroData>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for BrushMacroInstanceList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Visit for BrushMacroInstanceList {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
let mut count = self.0.len() as u32;
count.visit("Count", &mut region)?;
if region.is_reading() {
self.clear();
for i in 0..(count as usize) {
let name = i.to_string();
let mut value = BrushMacroData::default();
match value.visit(&name, &mut region) {
Ok(()) => self.0.push(value),
Err(err) => Log::err(format!("Failed to load brush tool data due to: {err}")),
}
}
} else {
for (i, value) in self.0.iter_mut().enumerate() {
let name = i.to_string();
value.visit(&name, &mut region)?;
}
}
Ok(())
}
}
#[derive(Debug, Default, Clone, Visit, Reflect)]
pub struct BrushMacroData {
pub macro_id: Uuid,
pub name: String,
pub settings: Option<UntypedResource>,
}
#[derive(Default, Debug, Clone, Visit, Reflect)]
pub struct TileMapBrushPage {
pub icon: TileDefinitionHandle,
#[reflect(hidden)]
pub tiles: Tiles,
}
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>,
#[visit(optional)]
pub macros: BrushMacroInstanceList,
#[reflect(hidden)]
#[visit(skip)]
pub change_flag: ChangeFlag,
}
impl TileMapBrush {
pub fn block_until_tile_set_is_loaded(&self) -> bool {
let Some(tile_set) = self.tile_set.as_ref() else {
return true;
};
let tile_set = match block_on(tile_set.clone()) {
Ok(tile_set) => tile_set,
Err(e) => {
Log::err(format!("Tile set load failed! Reason: {e:?}"));
return false;
}
};
if tile_set.is_ok() {
true
} else {
Log::err("Tile set load failed!");
false
}
}
pub fn tile_set(&self) -> Option<TileSetResource> {
let tile_set = self.tile_set.as_ref()?;
let tile_set = match block_on(tile_set.clone()) {
Ok(tile_set) => tile_set,
Err(e) => {
Log::err(format!("Tile set load failed! Reason: {e:?}"));
return None;
}
};
if !tile_set.is_ok() {
Log::err("Tile set load failed!");
return None;
}
Some(tile_set)
}
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() 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 = if p.icon.is_empty() {
TileRenderData::empty()
} else {
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 = if handle.is_empty() {
TileRenderData::empty()
} else {
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)?;
if handle.is_empty() {
return Some(TileRenderData::empty());
}
let tile_set = self.tile_set()?;
let mut tile_set = tile_set.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(&position.stage_position()).copied()
}
TilePaletteStage::Pages => self
.pages
.get(&position.stage_position())
.map(|page| page.icon),
}
}
pub fn stamp_element(&self, position: ResourceTilePosition) -> Option<StampElement> {
match position.stage() {
TilePaletteStage::Pages => self.redirect_handle(position).map(|handle| StampElement {
handle,
source: Some(position),
}),
TilePaletteStage::Tiles => {
let page = self.pages.get(&position.page())?;
Some(StampElement {
handle: *page.tiles.get(&position.stage_position())?,
source: Some(position),
})
}
}
}
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 fn is_free_at(&self, position: ResourceTilePosition) -> bool {
match position.stage() {
TilePaletteStage::Pages => !self.pages.contains_key(&position.stage_position()),
TilePaletteStage::Tiles => !self
.pages
.get(&position.page())
.map(|p| p.tiles.contains_key(&position.stage_position()))
.unwrap_or_default(),
}
}
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_ascii_to_file(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
}
fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
Some(Box::new(self.clone()))
}
}
pub struct TileMapBrushLoader {
pub resource_manager: ResourceManager,
}
impl ResourceLoader for TileMapBrushLoader {
fn extensions(&self) -> &[&str] {
&["tile_map_brush"]
}
fn is_native_extension(&self, ext: &str) -> bool {
fyrox_core::cmp_strings_case_insensitive(ext, "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>;