use randscape::grid::Grid;
use spitfire_draw::{
context::DrawContext,
tiles::{TileInstance, TileMap, TileSet, TilesEmitter},
utils::{Drawable, Vertex},
};
use spitfire_glow::graphics::GraphicsTarget;
use std::{any::Any, ops::Range};
use vek::{Rect, Vec2};
pub trait GridWorldEmitterFilter: Any {
fn filter(&self, tile: &TileInstance) -> bool;
}
impl GridWorldEmitterFilter for () {
fn filter(&self, _: &TileInstance) -> bool {
true
}
}
#[derive(Debug, Default)]
pub struct InRangeFilter {
pub location: Vec2<usize>,
pub range: usize,
pub clear_outside: bool,
}
impl GridWorldEmitterFilter for InRangeFilter {
fn filter(&self, tile: &TileInstance) -> bool {
let tile_location = tile.location.numcast::<isize>().unwrap();
let location = self.location.numcast::<isize>().unwrap();
let status = tile_location.distance_squared(location) as usize > self.range * self.range;
status != self.clear_outside
}
}
pub struct GridWorldLayer {
pub tilemap: TileMap,
pub visible: bool,
filter: Box<dyn GridWorldEmitterFilter>,
}
impl GridWorldLayer {
pub fn new(tilemap: TileMap, active: bool) -> Self {
Self {
tilemap,
visible: active,
filter: Box::new(()),
}
}
pub fn new_filtered<F: GridWorldEmitterFilter + 'static>(
tilemap: TileMap,
active: bool,
filter: F,
) -> Self {
Self {
tilemap,
visible: active,
filter: Box::new(filter),
}
}
pub fn access_filter<F: GridWorldEmitterFilter + 'static>(&self) -> Option<&F> {
let object: &dyn Any = &*self.filter;
object.downcast_ref::<F>()
}
pub fn access_filter_mut<F: GridWorldEmitterFilter + 'static>(&mut self) -> Option<&mut F> {
let object: &mut dyn Any = &mut *self.filter;
object.downcast_mut::<F>()
}
}
pub struct GridWorld {
pub position: Vec2<f32>,
pub pivot: Vec2<f32>,
pub tile_size: Vec2<f32>,
pub tileset: TileSet,
pub visible_layers: Range<usize>,
map_layers: Vec<GridWorldLayer>,
tile_instances: Vec<TileInstance>,
colliders: Grid<u8>,
}
impl GridWorld {
pub fn new(tile_size: Vec2<f32>, tileset: TileSet, terrain_layer: GridWorldLayer) -> Self {
let size = terrain_layer.tilemap.size();
Self {
position: Default::default(),
pivot: Default::default(),
tile_size,
tileset,
visible_layers: 0..1,
map_layers: vec![terrain_layer],
tile_instances: Default::default(),
colliders: Grid::new(size, 0),
}
}
pub fn from_layers(
tile_size: Vec2<f32>,
tileset: TileSet,
terrain_layers: impl IntoIterator<Item = GridWorldLayer>,
) -> Option<Self> {
let mut terrain_layers = terrain_layers.into_iter();
let layer = terrain_layers.next()?;
let mut result = Self::new(tile_size, tileset, layer);
for layer in terrain_layers {
result = result.with_layer(layer);
}
Some(result)
}
pub fn with_position(mut self, value: Vec2<f32>) -> Self {
self.position = value;
self
}
pub fn with_pivot(mut self, value: Vec2<f32>) -> Self {
self.pivot = value;
self
}
pub fn with_visible_layers(mut self, value: Range<usize>) -> Self {
self.visible_layers = value;
self
}
pub fn with_layer(mut self, layer: GridWorldLayer) -> Self {
if layer.tilemap.size() == self.map_layers[0].tilemap.size() {
self.map_layers.push(layer);
}
self
}
pub fn with_visible_layer(mut self, layer: GridWorldLayer) -> Self {
self = self.with_layer(layer);
self.visible_layers.end = self.map_layers.len();
self
}
pub fn with_tile_instance(mut self, instance: TileInstance) -> Self {
self.insert_tile_instance(instance);
self
}
pub fn with_tile_instances(
mut self,
instances: impl IntoIterator<Item = TileInstance>,
) -> Self {
for instance in instances {
self.insert_tile_instance(instance);
}
self
}
pub fn with_colliders(mut self, grid: Grid<u8>) -> Self {
if self.colliders.size() == grid.size() {
self.colliders = grid;
}
self
}
pub fn with_collider(mut self, location: Vec2<usize>) -> Self {
self.set_collider(location, true);
self
}
pub fn insert_tile_instance(&mut self, instance: TileInstance) {
if self.tile_instances.len() == self.tile_instances.capacity() {
self.tile_instances.reserve(self.tile_instances.capacity());
}
let index = self
.tile_instances
.binary_search_by(|item| item.location.yx().cmp(&instance.location.yx()))
.unwrap_or_else(|index| index);
self.tile_instances.insert(index, instance);
}
pub fn remove_tile_instances(&mut self, instance: &TileInstance) {
while let Some(index) = self.tile_instances.iter().position(|item| item == instance) {
self.tile_instances.remove(index);
}
}
pub fn remove_tile_instances_at_location(&mut self, location: Vec2<usize>) {
while let Some(index) = self
.tile_instances
.iter()
.position(|item| item.location == location)
{
self.tile_instances.remove(index);
}
}
pub fn collider(&self, location: Vec2<usize>) -> bool {
self.colliders.get(location).unwrap_or_default() > 0
}
pub fn set_collider(&mut self, location: Vec2<usize>, value: bool) -> u8 {
let old_value = self.colliders.get(location).unwrap_or_default();
self.colliders.set(
location,
if value {
old_value.saturating_add(1)
} else {
old_value.saturating_sub(1)
},
);
old_value
}
pub fn clear_collider(&mut self, location: Vec2<usize>) {
self.colliders.set(location, 0);
}
pub fn layers(&self) -> &[GridWorldLayer] {
&self.map_layers
}
pub fn layers_mut(&mut self) -> &mut [GridWorldLayer] {
&mut self.map_layers
}
pub fn locations_iter(&self) -> impl Iterator<Item = Vec2<usize>> {
let size = self.map_layers[0].tilemap.size();
(0..size.y).flat_map(move |y| (0..size.x).map(move |x| Vec2 { x, y }))
}
pub fn world_to_local(&self, location: Vec2<f32>) -> Option<Vec2<usize>> {
let size = self.map_layers[0].tilemap.size();
let result = location - self.position
+ Vec2::new(size.x as f32, size.y as f32) * self.tile_size * self.pivot;
let result = result / self.tile_size;
if result.x >= 0.0 && result.y >= 0.0 {
let result = Vec2::new(result.x as usize, result.y as usize);
if result.x < size.x && result.y < size.y {
return Some(result);
}
}
None
}
pub fn local_to_world(&self, location: Vec2<usize>) -> Vec2<f32> {
let size = self.map_layers[0].tilemap.size();
Vec2::new(location.x as f32, location.y as f32) * self.tile_size + self.position
- Vec2::new(size.x as f32, size.y as f32) * self.tile_size * self.pivot
}
}
impl Drawable for GridWorld {
fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
let size = self.map_layers[0].tilemap.size();
let rectangle = graphics.state().main_camera.world_rectangle();
let offset = (rectangle.position() - self.position) / self.tile_size;
let extent = rectangle.extent() / self.tile_size;
let region = Rect {
x: offset.x as usize,
y: offset.y as usize,
w: extent.w.ceil() as usize + 1,
h: extent.h.ceil() as usize + 1,
};
let offset = Vec2::new(size.x as f32, size.y as f32) * self.tile_size * self.pivot;
TilesEmitter::default()
.position(self.position - offset)
.tile_size(self.tile_size)
.emit(
&self.tileset,
self.visible_layers
.clone()
.filter_map(|index| self.map_layers.get(index))
.filter(|layer| layer.visible)
.flat_map(|layer| {
layer
.tilemap
.emit_region(region, false)
.filter(|tile| layer.filter.filter(tile))
})
.chain(
self.tile_instances
.iter()
.filter(|instance| {
self.tileset
.mappings
.get(&instance.id)
.map(|item| {
region.collides_with_rect(Rect {
x: instance.location.x - 1,
y: instance.location.y - 1,
w: item.size.x + 2,
h: item.size.y + 2,
})
})
.unwrap_or_default()
})
.cloned(),
),
)
.draw(context, graphics);
}
}