use fyrox_core::swap_hash_map_entry;
use crate::{
core::{algebra::Vector2, reflect::prelude::*, visitor::prelude::*},
fxhash::FxHashMap,
rand,
rand::seq::IteratorRandom,
};
use std::{
cmp::Ordering,
fmt::{Debug, Display, Formatter},
ops::{Deref, DerefMut},
str::FromStr,
};
use super::*;
pub type PalettePosition = Vector2<i16>;
#[inline]
fn try_position(source: Vector2<i32>) -> Option<PalettePosition> {
Some(PalettePosition::new(
source.x.try_into().ok()?,
source.y.try_into().ok()?,
))
}
#[inline]
fn position_to_vector(source: PalettePosition) -> Vector2<i32> {
source.map(|x| x as i32)
}
#[derive(Default, Debug, Clone, PartialEq, Reflect)]
pub struct TileGridMap<V: Debug + Clone + Reflect>(FxHashMap<Vector2<i32>, V>);
impl<V: Visit + Default + Debug + Clone + Reflect> Visit for TileGridMap<V> {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
self.0.visit(name, visitor)
}
}
impl<V: Debug + Clone + Reflect> Deref for TileGridMap<V> {
type Target = FxHashMap<Vector2<i32>, V>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<V: Debug + Clone + Reflect> DerefMut for TileGridMap<V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Eq, PartialEq, Clone, Copy, Default, Hash, Reflect, Visit, TypeUuidProvider)]
#[type_uuid(id = "3eb69303-d361-482d-8094-44b9f9c323ca")]
#[repr(C)]
pub struct TileDefinitionHandle {
pub page: PalettePosition,
pub tile: PalettePosition,
}
unsafe impl bytemuck::Zeroable for TileDefinitionHandle {}
unsafe impl bytemuck::Pod for TileDefinitionHandle {}
impl PartialOrd for TileDefinitionHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TileDefinitionHandle {
fn cmp(&self, other: &Self) -> Ordering {
self.page
.y
.cmp(&other.page.y)
.reverse()
.then(self.page.x.cmp(&other.page.x))
.then(self.tile.y.cmp(&other.tile.y).reverse())
.then(self.tile.x.cmp(&other.tile.x))
}
}
impl Display for TileDefinitionHandle {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.is_empty() {
f.write_str("Empty")
} else {
write!(
f,
"({},{}):({},{})",
self.page.x, self.page.y, self.tile.x, self.tile.y
)
}
}
}
impl Debug for TileDefinitionHandle {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"TileDefinitionHandle({},{};{},{})",
self.page.x, self.page.y, self.tile.x, self.tile.y
)
}
}
impl FromStr for TileDefinitionHandle {
type Err = TileDefinitionHandleParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s).ok_or(TileDefinitionHandleParseError)
}
}
#[derive(Debug)]
pub struct TileDefinitionHandleParseError;
impl Display for TileDefinitionHandleParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Tile definition handle parse failure")
}
}
impl Error for TileDefinitionHandleParseError {}
impl TileDefinitionHandle {
pub const EMPTY: Self = Self::new(i16::MIN, i16::MIN, i16::MIN, i16::MIN);
pub fn is_empty(&self) -> bool {
self == &Self::EMPTY
}
pub fn try_new(page: Vector2<i32>, tile: Vector2<i32>) -> Option<Self> {
Some(Self {
page: try_position(page)?,
tile: try_position(tile)?,
})
}
pub const fn new(page_x: i16, page_y: i16, tile_x: i16, tile_y: i16) -> Self {
Self {
page: PalettePosition::new(page_x, page_y),
tile: PalettePosition::new(tile_x, tile_y),
}
}
pub fn page(&self) -> Vector2<i32> {
position_to_vector(self.page)
}
pub fn tile(&self) -> Vector2<i32> {
position_to_vector(self.tile)
}
pub fn parse(s: &str) -> Option<Self> {
if s.eq_ignore_ascii_case("Empty") {
return Some(Self::EMPTY);
}
let mut iter = s
.split(|c: char| c != '-' && !c.is_ascii_digit())
.filter(|w| !w.is_empty());
let a: i16 = iter.next()?.parse().ok()?;
let b: i16 = iter.next()?.parse().ok()?;
let c: i16 = iter.next()?.parse().ok()?;
let d: i16 = iter.next()?.parse().ok()?;
if iter.next().is_some() {
None
} else {
Some(Self::new(a, b, c, d))
}
}
}
#[derive(Debug, Default, Clone)]
pub struct TileRegion {
pub origin: Vector2<i32>,
pub bounds: OptionTileRect,
}
impl TileRegion {
pub fn from_bounds_and_direction(bounds: OptionTileRect, direction: Vector2<i32>) -> Self {
let Some(bounds) = *bounds else {
return Self::default();
};
let x0 = if direction.x <= 0 {
bounds.left_bottom_corner().x
} else {
bounds.right_top_corner().x
};
let y0 = if direction.y <= 0 {
bounds.left_bottom_corner().y
} else {
bounds.right_top_corner().y
};
Self {
origin: Vector2::new(x0, y0),
bounds: bounds.into(),
}
}
pub fn from_points(origin: Vector2<i32>, end: Vector2<i32>) -> Self {
Self {
origin,
bounds: OptionTileRect::from_points(origin, end),
}
}
pub fn with_bounds(mut self, bounds: OptionTileRect) -> Self {
self.bounds = bounds;
self
}
pub fn deflate(mut self, dw: i32, dh: i32) -> Self {
self.bounds = self.bounds.deflate(dw, dh);
self
}
pub fn iter(&self) -> impl Iterator<Item = (Vector2<i32>, Vector2<i32>)> + '_ {
self.bounds.iter().map(|p| (p, p - self.origin))
}
}
pub trait TileSource {
fn brush(&self) -> Option<&TileMapBrushResource>;
fn transformation(&self) -> OrthoTransformation;
fn get_at(&self, position: Vector2<i32>) -> Option<StampElement>;
}
pub trait BoundedTileSource: TileSource {
fn bounding_rect(&self) -> OptionTileRect;
}
#[derive(Clone, Debug)]
pub struct SingleTileSource(pub OrthoTransformation, pub StampElement);
impl TileSource for SingleTileSource {
fn brush(&self) -> Option<&TileMapBrushResource> {
None
}
fn transformation(&self) -> OrthoTransformation {
self.0
}
fn get_at(&self, _position: Vector2<i32>) -> Option<StampElement> {
Some(self.1.clone())
}
}
pub struct RandomTileSource<'a>(pub &'a Stamp);
impl TileSource for RandomTileSource<'_> {
fn brush(&self) -> Option<&TileMapBrushResource> {
self.0.brush()
}
fn transformation(&self) -> OrthoTransformation {
self.0.transformation()
}
fn get_at(&self, _position: Vector2<i32>) -> Option<StampElement> {
self.0.values().choose(&mut rand::thread_rng()).cloned()
}
}
pub struct PartialRandomTileSource<'a>(pub &'a Stamp, pub OptionTileRect);
impl TileSource for PartialRandomTileSource<'_> {
fn brush(&self) -> Option<&TileMapBrushResource> {
self.0.brush()
}
fn transformation(&self) -> OrthoTransformation {
self.0.transformation()
}
fn get_at(&self, _position: Vector2<i32>) -> Option<StampElement> {
let pos = self.1.iter().choose(&mut rand::thread_rng())?;
self.0.get_at(pos)
}
}
pub struct RepeatTileSource<'a, S> {
pub source: &'a S,
pub region: TileRegion,
}
impl<S: TileSource> TileSource for RepeatTileSource<'_, S> {
fn brush(&self) -> Option<&TileMapBrushResource> {
self.source.brush()
}
fn transformation(&self) -> OrthoTransformation {
self.source.transformation()
}
fn get_at(&self, position: Vector2<i32>) -> Option<StampElement> {
let rect = (*self.region.bounds)?;
let rect_pos = rect.position;
let size = rect.size;
let pos = position + self.region.origin - rect_pos;
let x = pos.x.rem_euclid(size.x);
let y = pos.y.rem_euclid(size.y);
self.source.get_at(Vector2::new(x, y) + rect_pos)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Tiles(TileGridMap<TileDefinitionHandle>);
#[derive(Clone, Debug, Default, Visit)]
pub struct Stamp {
transform: OrthoTransformation,
#[visit(skip)]
elements: OrthoTransformMap<StampElement>,
#[visit(skip)]
source: Option<TileBook>,
}
#[derive(Clone, Debug, Reflect, Visit, Default)]
pub struct StampElement {
pub handle: TileDefinitionHandle,
pub source: Option<ResourceTilePosition>,
}
impl From<TileDefinitionHandle> for StampElement {
fn from(handle: TileDefinitionHandle) -> Self {
Self {
handle,
source: None,
}
}
}
impl TileSource for Tiles {
fn brush(&self) -> Option<&TileMapBrushResource> {
None
}
fn transformation(&self) -> OrthoTransformation {
OrthoTransformation::default()
}
fn get_at(&self, position: Vector2<i32>) -> Option<StampElement> {
self.get(&position).copied().map(|h| h.into())
}
}
impl Visit for Tiles {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
self.0.visit(name, visitor)
}
}
impl Deref for Tiles {
type Target = TileGridMap<TileDefinitionHandle>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Tiles {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl TileSource for Stamp {
fn brush(&self) -> Option<&TileMapBrushResource> {
self.source.as_ref().and_then(|s| s.brush_ref())
}
fn transformation(&self) -> OrthoTransformation {
self.transform
}
fn get_at(&self, position: Vector2<i32>) -> Option<StampElement> {
self.elements.get(position).cloned()
}
}
impl Stamp {
pub fn source(&self) -> Option<&TileBook> {
self.source.as_ref()
}
pub fn tile_iter(&self) -> impl Iterator<Item = TileDefinitionHandle> + '_ {
self.elements.values().map(|s| s.handle)
}
pub fn repeat(&self, start: Vector2<i32>, end: Vector2<i32>) -> RepeatTileSource<Stamp> {
let bounds = self.bounding_rect();
RepeatTileSource {
source: self,
region: TileRegion::from_bounds_and_direction(bounds, start - end),
}
}
pub fn repeat_anywhere(&self) -> RepeatTileSource<Stamp> {
let bounds = self.bounding_rect();
RepeatTileSource {
source: self,
region: TileRegion::from_bounds_and_direction(bounds, Vector2::new(0, 0)),
}
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn clear(&mut self) {
self.transform = OrthoTransformation::identity();
self.elements.clear();
}
pub fn build<I: Iterator<Item = (Vector2<i32>, StampElement)> + Clone>(
&mut self,
book: Option<TileBook>,
source: I,
) {
self.source = book;
self.clear();
let mut rect = OptionTileRect::default();
for (p, _) in source.clone() {
rect.push(p);
}
let Some(rect) = *rect else {
return;
};
let center = rect.center();
for (p, e) in source {
_ = self.insert(p - center, e);
}
}
pub fn rotate(&mut self, amount: i8) {
self.transform = self.transform.rotated(amount);
self.elements = std::mem::take(&mut self.elements).rotated(amount);
}
pub fn x_flip(&mut self) {
self.transform = self.transform.x_flipped();
self.elements = std::mem::take(&mut self.elements).x_flipped();
}
pub fn y_flip(&mut self) {
self.transform = self.transform.y_flipped();
self.elements = std::mem::take(&mut self.elements).y_flipped();
}
pub fn transform(&mut self, amount: OrthoTransformation) {
self.transform = self.transform.transformed(amount);
self.elements = std::mem::take(&mut self.elements).transformed(amount);
}
}
impl Deref for Stamp {
type Target = OrthoTransformMap<StampElement>;
fn deref(&self) -> &Self::Target {
&self.elements
}
}
impl DerefMut for Stamp {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.elements
}
}
impl Tiles {
pub fn new(source: TileGridMap<TileDefinitionHandle>) -> Self {
Self(source)
}
pub fn find_continuous_horizontal_span(&self, position: Vector2<i32>) -> (i32, i32) {
let y = position.y;
let mut min = position.x;
while self.contains_key(&Vector2::new(min, y)) {
min -= 1;
}
let mut max = position.x;
while self.contains_key(&Vector2::new(max, y)) {
max += 1;
}
(min, max)
}
pub fn swap_tiles(&mut self, updates: &mut TilesUpdate) {
for (k, v) in updates.iter_mut() {
swap_hash_map_entry(self.entry(*k), v);
}
}
#[inline]
pub fn bounding_rect(&self) -> OptionTileRect {
let mut result = OptionTileRect::default();
for position in self.keys() {
result.push(*position);
}
result
}
#[inline]
pub fn clear(&mut self) {
self.0.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_of_handle() {
assert_eq!(std::mem::size_of::<TileDefinitionHandle>(), 8);
}
#[test]
fn zero_handle() {
assert_eq!(
TileDefinitionHandle::zeroed(),
TileDefinitionHandle::default()
);
}
}