micro_games_kit/
grid_world.rs

1use crate::pcg::Grid;
2use spitfire_draw::{
3    context::DrawContext,
4    tiles::{TileInstance, TileMap, TileSet, TilesEmitter},
5    utils::{Drawable, Vertex},
6};
7use spitfire_glow::graphics::Graphics;
8use std::{
9    any::{Any, TypeId},
10    ops::Range,
11};
12use vek::{Rect, Vec2};
13
14pub trait GridWorldEmitterFilter: Any {
15    fn filter(&self, tile: &TileInstance) -> bool;
16}
17
18impl GridWorldEmitterFilter for () {
19    fn filter(&self, _: &TileInstance) -> bool {
20        true
21    }
22}
23
24#[derive(Debug, Default)]
25pub struct InRangeFilter {
26    pub location: Vec2<usize>,
27    pub range: usize,
28    pub clear_outside: bool,
29}
30
31impl GridWorldEmitterFilter for InRangeFilter {
32    fn filter(&self, tile: &TileInstance) -> bool {
33        let status = tile.location.distance_squared(self.location) > self.range * self.range;
34        status != self.clear_outside
35    }
36}
37
38pub struct GridWorldLayer {
39    pub tilemap: TileMap,
40    filter: Box<dyn GridWorldEmitterFilter>,
41    filter_type: TypeId,
42}
43
44impl GridWorldLayer {
45    pub fn new(tilemap: TileMap) -> Self {
46        Self {
47            tilemap,
48            filter: Box::new(()),
49            filter_type: TypeId::of::<()>(),
50        }
51    }
52
53    pub fn new_filtered<F: GridWorldEmitterFilter + 'static>(tilemap: TileMap, filter: F) -> Self {
54        Self {
55            tilemap,
56            filter: Box::new(filter),
57            filter_type: TypeId::of::<F>(),
58        }
59    }
60
61    pub fn access_filter<F: GridWorldEmitterFilter + 'static>(&mut self) -> Option<&mut F> {
62        if self.filter_type == TypeId::of::<F>() {
63            let result = &mut *self.filter as *mut dyn GridWorldEmitterFilter as *mut F;
64            unsafe { Some(&mut *result) }
65        } else {
66            None
67        }
68    }
69}
70
71pub struct GridWorld {
72    pub position: Vec2<f32>,
73    pub pivot: Vec2<f32>,
74    pub tile_size: Vec2<f32>,
75    pub tileset: TileSet,
76    pub visible_layers: Range<usize>,
77    map_layers: Vec<GridWorldLayer>,
78    tile_instances: Vec<TileInstance>,
79    colliders: Grid<bool>,
80}
81
82impl GridWorld {
83    pub fn new(tile_size: Vec2<f32>, tileset: TileSet, terrain_layer: GridWorldLayer) -> Self {
84        let size = terrain_layer.tilemap.size();
85        Self {
86            position: Default::default(),
87            pivot: Default::default(),
88            tile_size,
89            tileset,
90            visible_layers: 0..1,
91            map_layers: vec![terrain_layer],
92            tile_instances: Default::default(),
93            colliders: Grid::new(size, false),
94        }
95    }
96
97    pub fn with_position(mut self, value: Vec2<f32>) -> Self {
98        self.position = value;
99        self
100    }
101
102    pub fn with_pivot(mut self, value: Vec2<f32>) -> Self {
103        self.pivot = value;
104        self
105    }
106
107    pub fn with_visible_layers(mut self, value: Range<usize>) -> Self {
108        self.visible_layers = value;
109        self
110    }
111
112    pub fn with_layer(mut self, layer: GridWorldLayer) -> Self {
113        if layer.tilemap.size() == self.map_layers[0].tilemap.size() {
114            self.map_layers.push(layer);
115        }
116        self
117    }
118
119    pub fn with_visible_layer(mut self, layer: GridWorldLayer) -> Self {
120        self = self.with_layer(layer);
121        self.visible_layers.end = self.map_layers.len();
122        self
123    }
124
125    pub fn with_tile_instance(mut self, instance: TileInstance) -> Self {
126        self.insert_tile_instance(instance);
127        self
128    }
129
130    pub fn with_tile_instances(
131        mut self,
132        instances: impl IntoIterator<Item = TileInstance>,
133    ) -> Self {
134        for instance in instances {
135            self.insert_tile_instance(instance);
136        }
137        self
138    }
139
140    pub fn with_colliders(mut self, grid: Grid<bool>) -> Self {
141        if self.colliders.size() == grid.size() {
142            self.colliders = grid;
143        }
144        self
145    }
146
147    pub fn with_collider(mut self, location: Vec2<usize>) -> Self {
148        self.set_collider(location, true);
149        self
150    }
151
152    pub fn insert_tile_instance(&mut self, instance: TileInstance) {
153        if self.tile_instances.len() == self.tile_instances.capacity() {
154            self.tile_instances.reserve(self.tile_instances.capacity());
155        }
156        let index = self
157            .tile_instances
158            .binary_search_by(|item| item.location.yx().cmp(&instance.location.yx()))
159            .unwrap_or_else(|index| index);
160        self.tile_instances.insert(index, instance);
161    }
162
163    pub fn remove_tile_instances(&mut self, instance: &TileInstance) {
164        while let Some(index) = self.tile_instances.iter().position(|item| item == instance) {
165            self.tile_instances.remove(index);
166        }
167    }
168
169    pub fn remove_tile_instances_at_location(&mut self, location: Vec2<usize>) {
170        while let Some(index) = self
171            .tile_instances
172            .iter()
173            .position(|item| item.location == location)
174        {
175            self.tile_instances.remove(index);
176        }
177    }
178
179    pub fn collider(&self, location: Vec2<usize>) -> bool {
180        self.colliders.get(location).unwrap_or_default()
181    }
182
183    pub fn set_collider(&mut self, location: Vec2<usize>, value: bool) {
184        self.colliders.set(location, value);
185    }
186
187    pub fn layers(&self) -> &[GridWorldLayer] {
188        &self.map_layers
189    }
190
191    pub fn layers_mut(&mut self) -> &mut [GridWorldLayer] {
192        &mut self.map_layers
193    }
194
195    pub fn locations_iter(&self) -> impl Iterator<Item = Vec2<usize>> {
196        let size = self.map_layers[0].tilemap.size();
197        (0..size.y).flat_map(move |y| (0..size.x).map(move |x| Vec2 { x, y }))
198    }
199
200    pub fn world_to_local(&self, location: Vec2<f32>) -> Option<Vec2<usize>> {
201        let size = self.map_layers[0].tilemap.size();
202        let result = location - self.position
203            + Vec2::new(size.x as f32, size.y as f32) * self.tile_size * self.pivot;
204        let result = result / self.tile_size;
205        if result.x >= 0.0 && result.y >= 0.0 {
206            let result = Vec2::new(result.x as usize, result.y as usize);
207            if result.x < size.x && result.y < size.y {
208                return Some(result);
209            }
210        }
211        None
212    }
213
214    pub fn local_to_world(&self, location: Vec2<usize>) -> Vec2<f32> {
215        let size = self.map_layers[0].tilemap.size();
216        Vec2::new(location.x as f32, location.y as f32) * self.tile_size + self.position
217            - Vec2::new(size.x as f32, size.y as f32) * self.tile_size * self.pivot
218    }
219}
220
221impl Drawable for GridWorld {
222    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
223        let size = self.map_layers[0].tilemap.size();
224        let rectangle = graphics.main_camera.world_rectangle();
225        let offset = (rectangle.position() - self.position) / self.tile_size;
226        let extent = rectangle.extent() / self.tile_size;
227        let region = Rect {
228            x: offset.x as usize,
229            y: offset.y as usize,
230            w: extent.w.ceil() as usize + 1,
231            h: extent.h.ceil() as usize + 1,
232        };
233        let offset = Vec2::new(size.x as f32, size.y as f32) * self.tile_size * self.pivot;
234
235        TilesEmitter::default()
236            .position(self.position - offset)
237            .tile_size(self.tile_size)
238            .emit(
239                &self.tileset,
240                self.visible_layers
241                    .clone()
242                    .filter_map(|index| self.map_layers.get(index))
243                    .flat_map(|layer| {
244                        layer
245                            .tilemap
246                            .emit_region(region, false)
247                            .filter(|tile| layer.filter.filter(tile))
248                    })
249                    .chain(
250                        self.tile_instances
251                            .iter()
252                            .filter(|instance| {
253                                self.tileset
254                                    .mappings
255                                    .get(&instance.id)
256                                    .map(|item| {
257                                        region.collides_with_rect(Rect {
258                                            x: instance.location.x - 1,
259                                            y: instance.location.y - 1,
260                                            w: item.size.x + 2,
261                                            h: item.size.y + 2,
262                                        })
263                                    })
264                                    .unwrap_or_default()
265                            })
266                            .cloned(),
267                    ),
268            )
269            .draw(context, graphics);
270    }
271}