spitfire_draw/
tiles.rs

1use crate::{
2    context::DrawContext,
3    sprite::SpriteTexture,
4    utils::{Drawable, ShaderRef, Vertex},
5};
6use smallvec::SmallVec;
7use spitfire_glow::{
8    graphics::{Graphics, GraphicsBatch},
9    renderer::{GlowBlending, GlowUniformValue},
10};
11use std::{
12    borrow::Cow,
13    cell::RefCell,
14    collections::{HashMap, HashSet},
15    ops::{Index, IndexMut},
16};
17use vek::{Mat4, Quaternion, Rect, Rgba, Transform, Vec2, Vec3};
18
19#[derive(Debug, Clone)]
20pub struct TileSetItem {
21    pub region: Rect<f32, f32>,
22    pub page: f32,
23    pub tint: Rgba<f32>,
24    pub size: Vec2<usize>,
25    pub offset: Vec2<isize>,
26}
27
28impl Default for TileSetItem {
29    fn default() -> Self {
30        Self {
31            region: Rect::new(0.0, 0.0, 1.0, 1.0),
32            page: 0.0,
33            tint: Rgba::white(),
34            size: Vec2::new(1, 1),
35            offset: Default::default(),
36        }
37    }
38}
39
40impl TileSetItem {
41    pub fn region(mut self, value: Rect<f32, f32>) -> Self {
42        self.region = value;
43        self
44    }
45
46    pub fn page(mut self, value: f32) -> Self {
47        self.page = value;
48        self
49    }
50
51    pub fn tint(mut self, value: Rgba<f32>) -> Self {
52        self.tint = value;
53        self
54    }
55
56    pub fn size(mut self, value: Vec2<usize>) -> Self {
57        self.size = value;
58        self
59    }
60
61    pub fn offset(mut self, value: Vec2<isize>) -> Self {
62        self.offset = value;
63        self
64    }
65}
66
67#[derive(Debug, Default, Clone)]
68pub struct TileSet {
69    pub shader: Option<ShaderRef>,
70    pub textures: SmallVec<[SpriteTexture; 4]>,
71    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
72    pub blending: Option<GlowBlending>,
73    pub mappings: HashMap<usize, TileSetItem>,
74}
75
76impl TileSet {
77    pub fn single(texture: SpriteTexture) -> Self {
78        Self {
79            textures: vec![texture].into(),
80            ..Default::default()
81        }
82    }
83
84    pub fn shader(mut self, value: ShaderRef) -> Self {
85        self.shader = Some(value);
86        self
87    }
88
89    pub fn texture(mut self, value: SpriteTexture) -> Self {
90        self.textures.push(value);
91        self
92    }
93
94    pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
95        self.uniforms.insert(key, value);
96        self
97    }
98
99    pub fn blending(mut self, value: GlowBlending) -> Self {
100        self.blending = Some(value);
101        self
102    }
103
104    pub fn mapping(mut self, id: usize, item: TileSetItem) -> Self {
105        self.mappings.insert(id, item);
106        self
107    }
108
109    pub fn mappings(mut self, iter: impl IntoIterator<Item = (usize, TileSetItem)>) -> Self {
110        self.mappings.extend(iter);
111        self
112    }
113}
114
115#[derive(Debug, Default, Clone)]
116pub struct TilesEmitter {
117    pub transform: Transform<f32, f32, f32>,
118    pub tile_size: Vec2<f32>,
119    pub screen_space: bool,
120}
121
122impl TilesEmitter {
123    pub fn transform(mut self, value: Transform<f32, f32, f32>) -> Self {
124        self.transform = value;
125        self
126    }
127
128    pub fn position(mut self, value: Vec2<f32>) -> Self {
129        self.transform.position = value.into();
130        self
131    }
132
133    pub fn orientation(mut self, value: Quaternion<f32>) -> Self {
134        self.transform.orientation = value;
135        self
136    }
137
138    pub fn rotation(mut self, angle_radians: f32) -> Self {
139        self.transform.orientation = Quaternion::rotation_z(angle_radians);
140        self
141    }
142
143    pub fn scale(mut self, value: Vec2<f32>) -> Self {
144        self.transform.scale = Vec3::new(value.x, value.y, 1.0);
145        self
146    }
147
148    pub fn tile_size(mut self, value: Vec2<f32>) -> Self {
149        self.tile_size = value;
150        self
151    }
152
153    pub fn screen_space(mut self, value: bool) -> Self {
154        self.screen_space = value;
155        self
156    }
157
158    pub fn emit<'a, I: IntoIterator<Item = TileInstance>>(
159        &'a self,
160        set: &'a TileSet,
161        instances: I,
162    ) -> TilesDraw<'a, I> {
163        TilesDraw {
164            emitter: self,
165            tileset: set,
166            instances: RefCell::new(Some(instances)),
167        }
168    }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct TileInstance {
173    pub id: usize,
174    pub location: Vec2<usize>,
175}
176
177impl TileInstance {
178    pub fn new(id: usize, location: Vec2<usize>) -> Self {
179        Self { id, location }
180    }
181}
182
183pub struct TilesDraw<'a, I: IntoIterator<Item = TileInstance>> {
184    emitter: &'a TilesEmitter,
185    tileset: &'a TileSet,
186    instances: RefCell<Option<I>>,
187}
188
189impl<I: IntoIterator<Item = TileInstance>> Drawable for TilesDraw<'_, I> {
190    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
191        let batch = GraphicsBatch {
192            shader: context.shader(self.tileset.shader.as_ref()),
193            uniforms: self
194                .tileset
195                .uniforms
196                .iter()
197                .map(|(k, v)| (k.clone(), v.to_owned()))
198                .chain(std::iter::once((
199                    "u_projection_view".into(),
200                    GlowUniformValue::M4(
201                        if self.emitter.screen_space {
202                            graphics.main_camera.screen_matrix()
203                        } else {
204                            graphics.main_camera.world_matrix()
205                        }
206                        .into_col_array(),
207                    ),
208                )))
209                .chain(
210                    self.tileset
211                        .textures
212                        .iter()
213                        .enumerate()
214                        .map(|(index, texture)| {
215                            (texture.sampler.clone(), GlowUniformValue::I1(index as _))
216                        }),
217                )
218                .collect(),
219            textures: self
220                .tileset
221                .textures
222                .iter()
223                .filter_map(|texture| {
224                    Some((context.texture(Some(&texture.texture))?, texture.filtering))
225                })
226                .collect(),
227            blending: self
228                .tileset
229                .blending
230                .unwrap_or_else(|| context.top_blending()),
231            scissor: None,
232        };
233        graphics.stream.batch_optimized(batch);
234        let transform = Mat4::from(context.top_transform()) * Mat4::from(self.emitter.transform);
235        graphics.stream.transformed(
236            move |stream| {
237                let instances = match self.instances.borrow_mut().take() {
238                    Some(instances) => instances,
239                    None => return,
240                };
241                for instance in instances {
242                    if let Some(tile) = self.tileset.mappings.get(&instance.id) {
243                        let offset = Vec2 {
244                            x: (instance.location.x as isize + tile.offset.x) as f32,
245                            y: (instance.location.y as isize + tile.offset.y) as f32,
246                        } * self.emitter.tile_size;
247                        let size = Vec2 {
248                            x: tile.size.x as f32,
249                            y: tile.size.y as f32,
250                        } * self.emitter.tile_size;
251                        let color = tile.tint.into_array();
252                        stream.quad([
253                            Vertex {
254                                position: [offset.x, offset.y],
255                                uv: [tile.region.x, tile.region.y, tile.page],
256                                color,
257                            },
258                            Vertex {
259                                position: [offset.x + size.x, offset.y],
260                                uv: [tile.region.x + tile.region.w, tile.region.y, tile.page],
261                                color,
262                            },
263                            Vertex {
264                                position: [offset.x + size.x, offset.y + size.y],
265                                uv: [
266                                    tile.region.x + tile.region.w,
267                                    tile.region.y + tile.region.h,
268                                    tile.page,
269                                ],
270                                color,
271                            },
272                            Vertex {
273                                position: [offset.x, offset.y + size.y],
274                                uv: [tile.region.x, tile.region.y + tile.region.h, tile.page],
275                                color,
276                            },
277                        ]);
278                    }
279                }
280            },
281            |vertex| {
282                let point = transform.mul_point(Vec2::from(vertex.position));
283                vertex.position[0] = point.x;
284                vertex.position[1] = point.y;
285            },
286        );
287    }
288}
289
290#[derive(Debug, Clone)]
291pub struct TileMap {
292    pub include_ids: HashSet<usize>,
293    pub exclude_ids: HashSet<usize>,
294    size: Vec2<usize>,
295    buffer: Vec<usize>,
296}
297
298impl TileMap {
299    pub fn new(size: Vec2<usize>, fill_id: usize) -> Self {
300        Self {
301            include_ids: Default::default(),
302            exclude_ids: Default::default(),
303            size,
304            buffer: vec![fill_id; size.x * size.y],
305        }
306    }
307
308    pub fn with_buffer(size: Vec2<usize>, buffer: Vec<usize>) -> Option<Self> {
309        if buffer.len() == size.x * size.y {
310            Some(Self {
311                include_ids: Default::default(),
312                exclude_ids: Default::default(),
313                size,
314                buffer,
315            })
316        } else {
317            None
318        }
319    }
320
321    pub fn size(&self) -> Vec2<usize> {
322        self.size
323    }
324
325    pub fn buffer(&self) -> &[usize] {
326        &self.buffer
327    }
328
329    pub fn buffer_mut(&mut self) -> &mut [usize] {
330        &mut self.buffer
331    }
332
333    pub fn index(&self, location: impl Into<Vec2<usize>>) -> usize {
334        let location = location.into();
335        (location.y % self.size.y) * self.size.x + (location.x % self.size.x)
336    }
337
338    pub fn location(&self, index: usize) -> Vec2<usize> {
339        Vec2 {
340            x: index % self.size.x,
341            y: (index / self.size.y) % self.size.y,
342        }
343    }
344
345    pub fn get(&self, location: impl Into<Vec2<usize>>) -> Option<usize> {
346        let index = self.index(location);
347        self.buffer.get(index).copied()
348    }
349
350    pub fn set(&mut self, location: impl Into<Vec2<usize>>, id: usize) {
351        let index = self.index(location);
352        if let Some(item) = self.buffer.get_mut(index) {
353            *item = id;
354        }
355    }
356
357    pub fn fill(&mut self, from: impl Into<Vec2<usize>>, to: impl Into<Vec2<usize>>, id: usize) {
358        let from = from.into();
359        let to = to.into();
360        for y in from.y..to.y {
361            for x in from.x..to.x {
362                self.set(Vec2::new(x, y), id);
363            }
364        }
365    }
366
367    pub fn is_id_valid(&self, id: usize) -> bool {
368        (self.include_ids.is_empty() || self.include_ids.contains(&id))
369            && (self.exclude_ids.is_empty() || !self.exclude_ids.contains(&id))
370    }
371
372    pub fn emit(&self) -> impl Iterator<Item = TileInstance> + '_ {
373        self.buffer.iter().enumerate().filter_map(|(index, id)| {
374            if self.is_id_valid(*id) {
375                Some(TileInstance {
376                    id: *id,
377                    location: self.location(index),
378                })
379            } else {
380                None
381            }
382        })
383    }
384
385    pub fn emit_region(
386        &self,
387        region: impl Into<Rect<usize, usize>>,
388        repeating: bool,
389    ) -> impl Iterator<Item = TileInstance> + '_ {
390        let mut region = region.into();
391        if !repeating {
392            if region.x + region.w > self.size.x {
393                region.w = self.size.x.saturating_sub(region.x);
394            }
395            if region.y + region.h > self.size.y {
396                region.h = self.size.y.saturating_sub(region.y);
397            }
398        }
399        (region.y..(region.y + region.h)).flat_map(move |y| {
400            (region.x..(region.x + region.w)).filter_map(move |x| {
401                let location = Vec2 { x, y };
402                if let Some(id) = self.get(location) {
403                    if self.is_id_valid(id) {
404                        return Some(TileInstance { id, location });
405                    }
406                }
407                None
408            })
409        })
410    }
411}
412
413impl<T: Into<Vec2<usize>>> Index<T> for TileMap {
414    type Output = usize;
415
416    fn index(&self, location: T) -> &Self::Output {
417        let location = location.into();
418        let index = self.index(location);
419        self.buffer
420            .get(index)
421            .unwrap_or_else(|| panic!("Invalid location: {}", location))
422    }
423}
424
425impl<T: Into<Vec2<usize>>> IndexMut<T> for TileMap {
426    fn index_mut(&mut self, location: T) -> &mut Self::Output {
427        let location = location.into();
428        let index = self.index(location);
429        self.buffer
430            .get_mut(index)
431            .unwrap_or_else(|| panic!("Invalid location: {}", location))
432    }
433}