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}