spitfire_draw/
tiles.rs

1use crate::{
2    context::DrawContext,
3    sprite::SpriteTexture,
4    utils::{Drawable, ShaderRef, Vertex, transform_to_matrix},
5};
6use smallvec::SmallVec;
7use spitfire_glow::{
8    graphics::{GraphicsBatch, GraphicsTarget},
9    renderer::{GlowBlending, GlowUniformValue},
10};
11use std::{
12    borrow::Cow,
13    cell::RefCell,
14    collections::{HashMap, HashSet},
15    ops::{Index, IndexMut},
16};
17use vek::{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    pub offset: Vec2<isize>,
176}
177
178impl TileInstance {
179    pub fn new(id: usize, location: Vec2<usize>) -> Self {
180        Self {
181            id,
182            location,
183            offset: Default::default(),
184        }
185    }
186
187    pub fn offset(mut self, value: Vec2<isize>) -> Self {
188        self.offset = value;
189        self
190    }
191}
192
193pub struct TilesDraw<'a, I: IntoIterator<Item = TileInstance>> {
194    emitter: &'a TilesEmitter,
195    tileset: &'a TileSet,
196    instances: RefCell<Option<I>>,
197}
198
199impl<I: IntoIterator<Item = TileInstance>> Drawable for TilesDraw<'_, I> {
200    fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
201        let batch = GraphicsBatch {
202            shader: context.shader(self.tileset.shader.as_ref()),
203            uniforms: self
204                .tileset
205                .uniforms
206                .iter()
207                .map(|(k, v)| (k.clone(), v.to_owned()))
208                .chain(std::iter::once((
209                    "u_projection_view".into(),
210                    GlowUniformValue::M4(
211                        if self.emitter.screen_space {
212                            graphics.state().main_camera.screen_matrix()
213                        } else {
214                            graphics.state().main_camera.world_matrix()
215                        }
216                        .into_col_array(),
217                    ),
218                )))
219                .chain(
220                    self.tileset
221                        .textures
222                        .iter()
223                        .enumerate()
224                        .map(|(index, texture)| {
225                            (texture.sampler.clone(), GlowUniformValue::I1(index as _))
226                        }),
227                )
228                .collect(),
229            textures: self
230                .tileset
231                .textures
232                .iter()
233                .filter_map(|texture| {
234                    Some((context.texture(Some(&texture.texture))?, texture.filtering))
235                })
236                .collect(),
237            blending: self
238                .tileset
239                .blending
240                .unwrap_or_else(|| context.top_blending()),
241            scissor: None,
242            wireframe: context.wireframe,
243        };
244        graphics.state_mut().stream.batch_optimized(batch);
245        let transform = context.top_transform() * transform_to_matrix(self.emitter.transform);
246        graphics.state_mut().stream.transformed(
247            move |stream| {
248                let instances = match self.instances.borrow_mut().take() {
249                    Some(instances) => instances,
250                    None => return,
251                };
252                for instance in instances {
253                    if let Some(tile) = self.tileset.mappings.get(&instance.id) {
254                        let offset = Vec2 {
255                            x: (instance.location.x as isize + instance.offset.x + tile.offset.x)
256                                as f32,
257                            y: (instance.location.y as isize + instance.offset.y + tile.offset.y)
258                                as f32,
259                        } * self.emitter.tile_size;
260                        let size = Vec2 {
261                            x: tile.size.x as f32,
262                            y: tile.size.y as f32,
263                        } * self.emitter.tile_size;
264                        let color = tile.tint.into_array();
265                        stream.quad([
266                            Vertex {
267                                position: [offset.x, offset.y],
268                                uv: [tile.region.x, tile.region.y, tile.page],
269                                color,
270                            },
271                            Vertex {
272                                position: [offset.x + size.x, offset.y],
273                                uv: [tile.region.x + tile.region.w, tile.region.y, tile.page],
274                                color,
275                            },
276                            Vertex {
277                                position: [offset.x + size.x, offset.y + size.y],
278                                uv: [
279                                    tile.region.x + tile.region.w,
280                                    tile.region.y + tile.region.h,
281                                    tile.page,
282                                ],
283                                color,
284                            },
285                            Vertex {
286                                position: [offset.x, offset.y + size.y],
287                                uv: [tile.region.x, tile.region.y + tile.region.h, tile.page],
288                                color,
289                            },
290                        ]);
291                    }
292                }
293            },
294            |vertex| {
295                let point = transform.mul_point(Vec2::from(vertex.position));
296                vertex.position[0] = point.x;
297                vertex.position[1] = point.y;
298            },
299        );
300    }
301}
302
303#[derive(Debug, Clone)]
304pub struct TileMap {
305    pub include_ids: HashSet<usize>,
306    pub exclude_ids: HashSet<usize>,
307    size: Vec2<usize>,
308    buffer: Vec<usize>,
309}
310
311impl TileMap {
312    pub fn new(size: Vec2<usize>, fill_id: usize) -> Self {
313        Self {
314            include_ids: Default::default(),
315            exclude_ids: Default::default(),
316            size,
317            buffer: vec![fill_id; size.x * size.y],
318        }
319    }
320
321    pub fn with_buffer(size: Vec2<usize>, buffer: Vec<usize>) -> Option<Self> {
322        if buffer.len() == size.x * size.y {
323            Some(Self {
324                include_ids: Default::default(),
325                exclude_ids: Default::default(),
326                size,
327                buffer,
328            })
329        } else {
330            None
331        }
332    }
333
334    pub fn size(&self) -> Vec2<usize> {
335        self.size
336    }
337
338    pub fn buffer(&self) -> &[usize] {
339        &self.buffer
340    }
341
342    pub fn buffer_mut(&mut self) -> &mut [usize] {
343        &mut self.buffer
344    }
345
346    pub fn index(&self, location: impl Into<Vec2<usize>>) -> usize {
347        let location = location.into();
348        (location.y % self.size.y) * self.size.x + (location.x % self.size.x)
349    }
350
351    pub fn location(&self, index: usize) -> Vec2<usize> {
352        Vec2 {
353            x: index % self.size.x,
354            y: (index / self.size.y) % self.size.y,
355        }
356    }
357
358    pub fn get(&self, location: impl Into<Vec2<usize>>) -> Option<usize> {
359        let index = self.index(location);
360        self.buffer.get(index).copied()
361    }
362
363    pub fn set(&mut self, location: impl Into<Vec2<usize>>, id: usize) {
364        let index = self.index(location);
365        if let Some(item) = self.buffer.get_mut(index) {
366            *item = id;
367        }
368    }
369
370    pub fn fill(&mut self, from: impl Into<Vec2<usize>>, to: impl Into<Vec2<usize>>, id: usize) {
371        let from = from.into();
372        let to = to.into();
373        for y in from.y..to.y {
374            for x in from.x..to.x {
375                self.set(Vec2::new(x, y), id);
376            }
377        }
378    }
379
380    pub fn is_id_valid(&self, id: usize) -> bool {
381        (self.include_ids.is_empty() || self.include_ids.contains(&id))
382            && (self.exclude_ids.is_empty() || !self.exclude_ids.contains(&id))
383    }
384
385    pub fn emit(&self) -> impl Iterator<Item = TileInstance> + '_ {
386        self.buffer.iter().enumerate().filter_map(|(index, id)| {
387            if self.is_id_valid(*id) {
388                Some(TileInstance {
389                    id: *id,
390                    location: self.location(index),
391                    offset: Default::default(),
392                })
393            } else {
394                None
395            }
396        })
397    }
398
399    pub fn emit_region(
400        &self,
401        region: impl Into<Rect<usize, usize>>,
402        repeating: bool,
403    ) -> impl Iterator<Item = TileInstance> + '_ {
404        let mut region = region.into();
405        if !repeating {
406            if region.x + region.w > self.size.x {
407                region.w = self.size.x.saturating_sub(region.x);
408            }
409            if region.y + region.h > self.size.y {
410                region.h = self.size.y.saturating_sub(region.y);
411            }
412        }
413        (region.y..(region.y + region.h)).flat_map(move |y| {
414            (region.x..(region.x + region.w)).filter_map(move |x| {
415                let location = Vec2 { x, y };
416                if let Some(id) = self.get(location)
417                    && self.is_id_valid(id)
418                {
419                    return Some(TileInstance {
420                        id,
421                        location,
422                        offset: Default::default(),
423                    });
424                }
425                None
426            })
427        })
428    }
429}
430
431impl<T: Into<Vec2<usize>>> Index<T> for TileMap {
432    type Output = usize;
433
434    fn index(&self, location: T) -> &Self::Output {
435        let location = location.into();
436        let index = self.index(location);
437        self.buffer
438            .get(index)
439            .unwrap_or_else(|| panic!("Invalid location: {location}"))
440    }
441}
442
443impl<T: Into<Vec2<usize>>> IndexMut<T> for TileMap {
444    fn index_mut(&mut self, location: T) -> &mut Self::Output {
445        let location = location.into();
446        let index = self.index(location);
447        self.buffer
448            .get_mut(index)
449            .unwrap_or_else(|| panic!("Invalid location: {location}"))
450    }
451}