1
//! Types for rendering tilemaps. Requires `unstable-apis` feature flag.
2
//!
3
//! This module will likely be removed from `kludgine` and relocated to the
4
//! [`gooey-renderer`](https://gooey.rs/main/gooey/renderer/trait.Renderer.html)
5
//! ecosystem. The refactored tilemap will still be usable by `kludgine` apps
6
//! without using a `gooey` user interface, it will just use the `Renderer`
7
//! trait instead of directly interacting with a `Target`.
8

            
9
use std::mem;
10
use std::ops::{Deref, DerefMut};
11
use std::time::Duration;
12

            
13
use kludgine_core::figures::{Displayable, One, Rectlike, SizedRect};
14
use kludgine_core::math::{ExtentsRect, Figure, Pixels, Point, Scale, Scaled, Size, Unknown};
15
use kludgine_core::scene::Target;
16
use kludgine_core::sprite::{Sprite, SpriteRotation, SpriteSource};
17

            
18
/// `TileMap` renders tiles retrieved from a [`TileProvider`]
19
#[derive(Debug)]
20
pub struct TileMap<P> {
21
    provider: P,
22
    tile_size: Size<u32, Scaled>,
23
    stagger: Option<Size<u32, Scaled>>,
24
}
25

            
26
impl<P> TileMap<P>
27
where
28
    P: TileProvider,
29
{
30
    /// Creates a new tile map of the given size.
31
    pub fn new(tile_size: Size<u32, Scaled>, provider: P) -> Self {
32
        Self {
33
            tile_size,
34
            provider,
35
            stagger: None,
36
        }
37
    }
38

            
39
    /// Sets the stagger. This causes every odd row of tiles to be offset by
40
    /// `stagger` when rendered. This is commmonly used when creating isometric
41
    /// tile maps.
42
    pub fn set_stagger(&mut self, stagger: Size<u32, Scaled>) {
43
        self.stagger = Some(stagger);
44
    }
45

            
46
    /// Renders the tilemap. The tilemap will fill the `target`, but will be
47
    /// offset by `location`.
48
    pub fn render(
49
        &mut self,
50
        target: &Target,
51
        location: Point<f32, Scaled>,
52
    ) -> kludgine_core::Result<()> {
53
        self.render_scaled(target, location, Scale::one())
54
    }
55

            
56
    /// Renders the tilemap scaled by `scale`. The tilemap will fill the
57
    /// `target`, but will be offset by `location`.
58
    pub fn render_scaled(
59
        &mut self,
60
        scene: &Target,
61
        location: Point<f32, Scaled>,
62
        scale: Scale<f32, Unknown, Scaled>,
63
    ) -> kludgine_core::Result<()> {
64
        let tile_height = if let Some(stagger) = &self.stagger {
65
            stagger.height
66
        } else {
67
            self.tile_size.height
68
        };
69
        let tile_size = Size::<u32>::new(self.tile_size.width, tile_height).cast::<f32>() * scale;
70

            
71
        // We need to start at the upper-left of inverting the location
72
        let min_x = (-location.x / tile_size.width).floor() as i32;
73
        let min_y = (-location.y / tile_size.height).floor() as i32;
74
        let extra_x = tile_size.width() - Figure::new(1.);
75
        let extra_y = tile_size.height() - Figure::new(1.);
76
        let scene_size = scene.size();
77
        let total_width = scene_size.width() + extra_x;
78
        let total_height = scene_size.height() + extra_y;
79
        let tiles_wide = (total_width / tile_size.width as f32).get().ceil() as i32;
80
        let tiles_high = (total_height / tile_size.height as f32).get().ceil() as i32;
81

            
82
        let elapsed = scene.elapsed();
83

            
84
        let effective_scale = scene.scale();
85
        let tile_size = tile_size.to_pixels(effective_scale);
86
        let render_size = self.tile_size.cast::<f32>()
87
            * Scale::new(effective_scale.total_scale().get() * scale.get());
88
        let location = location.to_pixels(effective_scale);
89
        let mut y_pos = tile_size.height() * min_y as f32 + location.y();
90
        for y in min_y..(min_y + tiles_high) {
91
            let mut x_pos = tile_size.width() * min_x as f32 + location.x();
92
            if let Some(stagger) = &self.stagger {
93
                if y % 2 == 0 {
94
                    x_pos -=
95
                        Figure::<f32, Scaled>::new(stagger.width as f32).to_pixels(effective_scale);
96
                }
97
            }
98
            let next_y = y_pos + tile_size.height();
99
            for x in min_x..(min_x + tiles_wide) {
100
                let next_x = x_pos + tile_size.width();
101
                self.draw_one_tile(
102
                    Point::new(x, y),
103
                    SizedRect::new(Point::from_figures(x_pos, y_pos), render_size).as_extents(),
104
                    scene,
105
                    elapsed,
106
                )?;
107
                x_pos = next_x;
108
            }
109
            y_pos = next_y;
110
        }
111

            
112
        Ok(())
113
    }
114

            
115
    fn draw_one_tile(
116
        &mut self,
117
        tile: Point<i32>,
118
        destination: ExtentsRect<f32, Pixels>,
119
        scene: &Target,
120
        elapsed: Option<Duration>,
121
    ) -> kludgine_core::Result<()> {
122
        if let Some(mut tile) = self.provider.tile(tile) {
123
            let sprite = tile.sprite.get_frame(elapsed)?;
124
            sprite.render_raw_with_alpha_in_box(scene, destination, SpriteRotation::none(), 1.);
125
        }
126
        Ok(())
127
    }
128
}
129

            
130
/// `TileProvider` provides [`Tile`]s for a  [`TileMap`].
131
pub trait TileProvider {
132
    /// Returns the tile for `location`.
133
    fn tile(&mut self, location: Point<i32>) -> Option<Tile<'_>>;
134
}
135

            
136
/// A tile's sprite.
137
#[derive(Debug)]
138
pub enum TileSprite<'a> {
139
    /// A sprite that may be animated.
140
    Sprite(&'a mut Sprite),
141
    /// A single frame image.
142
    SpriteSource(SpriteSource),
143
}
144

            
145
impl<'a> From<&'a mut Sprite> for TileSprite<'a> {
146
    fn from(sprite: &'a mut Sprite) -> Self {
147
        Self::Sprite(sprite)
148
    }
149
}
150

            
151
impl<'a> From<SpriteSource> for TileSprite<'a> {
152
    fn from(sprite: SpriteSource) -> Self {
153
        Self::SpriteSource(sprite)
154
    }
155
}
156

            
157
impl<'a> TileSprite<'a> {
158
    /// Returns the current frame to display.
159
    pub fn get_frame(&mut self, elapsed: Option<Duration>) -> kludgine_core::Result<SpriteSource> {
160
        match self {
161
            TileSprite::Sprite(sprite) => sprite.get_frame(elapsed),
162
            TileSprite::SpriteSource(source) => Ok(source.clone()),
163
        }
164
    }
165
}
166

            
167
/// A Tile represents a sprite at an integer offset on the map
168
#[derive(Debug)]
169
pub struct Tile<'a> {
170
    /// The location of the tile.
171
    pub location: Point<i32>,
172
    /// The sprite to render for the tile.
173
    pub sprite: TileSprite<'a>,
174
}
175

            
176
/// Provides a simple interface for tile maps that have specific bounds
177
#[derive(Debug)]
178
pub struct PersistentTileProvider {
179
    tiles: Vec<Option<PersistentTileSource>>,
180
    dimensions: Size<u32>,
181
}
182

            
183
/// A tile sprite source for [`PersistentTileProvider`].
184
#[derive(Debug)]
185
pub enum PersistentTileSource {
186
    /// A sprite.
187
    Sprite(Sprite),
188
    /// A sprite source.
189
    SpriteSource(SpriteSource),
190
}
191

            
192
impl From<Sprite> for PersistentTileSource {
193
    fn from(sprite: Sprite) -> Self {
194
        Self::Sprite(sprite)
195
    }
196
}
197

            
198
impl<'a> From<SpriteSource> for PersistentTileSource {
199
    fn from(sprite: SpriteSource) -> Self {
200
        Self::SpriteSource(sprite)
201
    }
202
}
203

            
204
impl PersistentTileSource {
205
    fn as_tile(&mut self, location: Point<i32>) -> Tile<'_> {
206
        Tile {
207
            location,
208
            sprite: match self {
209
                PersistentTileSource::Sprite(sprite) => TileSprite::Sprite(sprite),
210
                PersistentTileSource::SpriteSource(source) => {
211
                    TileSprite::SpriteSource(source.clone())
212
                }
213
            },
214
        }
215
    }
216
}
217

            
218
impl TileProvider for PersistentTileProvider {
219
    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
220
    fn tile(&mut self, location: Point<i32>) -> Option<Tile<'_>> {
221
        if location.x < 0
222
            || location.y < 0
223
            || location.x >= self.dimensions.width as i32
224
            || location.y >= self.dimensions.height as i32
225
        {
226
            return None;
227
        }
228

            
229
        let index = self.point_to_index(Point::new(location.x as u32, location.y as u32));
230
        self.tiles
231
            .get_mut(index)
232
            .and_then(|tile| tile.as_mut().map(|t| t.as_tile(location)))
233
    }
234
}
235

            
236
impl PersistentTileProvider {
237
    /// Returns a blank map with dimensions `dimensions`.
238
    #[must_use]
239
    pub fn blank(dimensions: Size<u32>) -> Self {
240
        let mut tiles = Vec::new();
241
        tiles.resize_with((dimensions.width * dimensions.height) as usize, || {
242
            Option::<Sprite>::None
243
        });
244
        Self::new(dimensions, tiles)
245
    }
246

            
247
    /// Creates a new map using `tiles` with `dimensions`. Tiles are initialized
248
    /// from left-to-right then top-to-bottom.
249
    #[must_use]
250
    pub fn new<S: Into<PersistentTileSource>>(
251
        dimensions: Size<u32>,
252
        tiles: Vec<Option<S>>,
253
    ) -> Self {
254
        let tiles = tiles
255
            .into_iter()
256
            .map(|sprite| sprite.map(Into::into))
257
            .collect();
258
        Self { tiles, dimensions }
259
    }
260

            
261
    /// Sets a single tile at `location`. Returns the existing tile, if one was
262
    /// set.
263
    ///
264
    /// # Panics
265
    ///
266
    /// Panics if `location` is outside of the bounds of this map.
267
    pub fn set<I: Into<PersistentTileSource>>(
268
        &mut self,
269
        location: Point<u32>,
270
        sprite: Option<I>,
271
    ) -> Option<PersistentTileSource> {
272
        let index = self.point_to_index(location);
273
        mem::replace(&mut self.tiles[index], sprite.map(Into::into))
274
    }
275

            
276
    const fn point_to_index(&self, location: Point<u32>) -> usize {
277
        (location.x + location.y * self.dimensions.width) as usize
278
    }
279
}
280

            
281
/// `PersistentTileMap` is an alias for
282
/// [`TileMap`]`<`[`PersistentTileProvider`]`>`
283
pub type PersistentTileMap = TileMap<PersistentTileProvider>;
284

            
285
/// Convenience trait for creating persistent tile maps.
286
pub trait PersistentMap {
287
    /// Creates a [`TileMap`] using a [`PersistentTileProvider`].
288
    ///
289
    /// # Arguments
290
    ///
291
    /// * `tile_size`: The dimensions of each tile
292
    /// * `map_size`: The size of the map, in number of tiles
293
    fn persistent_with_size(tile_size: Size<u32, Scaled>, map_size: Size<u32>) -> Self;
294

            
295
    /// Sets a single tile at `location`. Returns the existing tile, if one was
296
    /// set.
297
    ///
298
    /// # Panics
299
    ///
300
    /// Panics if `location` is outside of the bounds of this map.
301
    fn set<I: Into<PersistentTileSource>>(&mut self, location: Point<u32>, sprite: Option<I>);
302
}
303

            
304
impl PersistentMap for PersistentTileMap {
305
    fn persistent_with_size(tile_size: Size<u32, Scaled>, map_size: Size<u32>) -> Self {
306
        Self::new(tile_size, PersistentTileProvider::blank(map_size))
307
    }
308

            
309
    fn set<I: Into<PersistentTileSource>>(&mut self, location: Point<u32>, sprite: Option<I>) {
310
        self.provider.set(location, sprite.map(Into::into));
311
    }
312
}
313

            
314
impl<T> Deref for TileMap<T> {
315
    type Target = T;
316

            
317
    fn deref(&self) -> &T {
318
        &self.provider
319
    }
320
}
321

            
322
impl<T> DerefMut for TileMap<T> {
323
    fn deref_mut(&mut self) -> &mut Self::Target {
324
        &mut self.provider
325
    }
326
}