Skip to main content

auburn/col2d/shape/
tilemap.rs

1use std::io::{BufReader, BufWriter, Write};
2
3use lk_math::arraynd::Array2d;
4use lk_math::modular::ModularDecompose;
5use lk_math::vector::V2i32;
6use round_to::*;
7
8use serde::{Deserialize, Serialize};
9use serde_with::serde_as;
10
11use super::{
12    Ball, Box2d, CollidesRel2d, PenetratesRel2d, Point, SymmetricBoundingBox2d, Transformation2d,
13    Vec2,
14};
15
16use crate::utils::publisher::{Ledger, Publisher};
17use crate::utils::rect2::Rect2i32;
18
19const CHUNK_SIZE: V2i32 = V2i32::from_xy(16, 16);
20
21#[derive(Debug)]
22pub enum TilemapEvent {
23    ChunkCreated(V2i32),
24    ChunkRemoved(V2i32),
25    ChunkChanged(V2i32),
26}
27
28#[serde_as]
29#[derive(Default, Serialize, Deserialize)]
30pub struct Tilemap {
31    // NOTE(lubo): Have to use `std::collections::HashMap` because `serde_as` is not implemented for
32    #[serde_as(as = "Vec<(_, _)>")]
33    chunks: std::collections::HashMap<V2i32, Chunk>,
34    #[serde(skip)]
35    // pub events: impl Publisher<TilemapEvent>,
36    pub events: Ledger<TilemapEvent>,
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40pub struct Chunk {
41    pub array2d: Array2d<u8>,
42}
43
44impl Default for Chunk {
45    fn default() -> Self {
46        Self {
47            array2d: Array2d::with_dimensions(CHUNK_SIZE.x() as usize, CHUNK_SIZE.y() as usize, 0),
48        }
49    }
50}
51
52impl Tilemap {
53    pub fn world_to_tile_pos(&self, world_pos: &Vec2) -> V2i32 {
54        V2i32::from_xy(world_pos.x.round_to_i32(), world_pos.y.round_to_i32())
55        // V2i32::from_xy(world_pos.x.floor_to_i32(), world_pos.y.floor_to_i32())
56    }
57    pub fn tile_to_world_pos(&self, tile_pos: &V2i32) -> Vec2 {
58        Vec2::new(tile_pos.x() as f32, tile_pos.y() as f32)
59    }
60    pub fn quantize(&self, t: &Box2d, world_pos: &Vec2) -> Rect2i32 {
61        Rect2i32 {
62            min: self.world_to_tile_pos(&(*world_pos - t.halfsize)),
63            max: self.world_to_tile_pos(&(*world_pos + t.halfsize)),
64        }
65    }
66
67    pub fn tile_to_chunk_and_local_pos(&self, tile_pos: &V2i32) -> (V2i32, V2i32) {
68        tile_pos.modular_decompose(CHUNK_SIZE)
69    }
70
71    pub fn tile_to_chunk_pos(&self, tile_pos: &V2i32) -> V2i32 {
72        let (chunk_pos, local) = tile_pos.modular_decompose(CHUNK_SIZE);
73        chunk_pos
74    }
75
76    pub fn get_chunk(&self, chunk_pos: &V2i32) -> Option<&'_ Chunk> {
77        self.chunks.get(&chunk_pos)
78    }
79
80    pub fn get_tile(&self, pos: V2i32) -> u8 {
81        let (chunk_pos, local) = pos.modular_decompose(CHUNK_SIZE);
82        if let Some(chunk) = self.chunks.get(&chunk_pos) {
83            *chunk.array2d.get(local).unwrap_or(&0)
84        } else {
85            0
86        }
87    }
88
89    pub fn set_tile(&mut self, pos: V2i32, tile: u8) {
90        let (chunk_pos, local) = pos.modular_decompose(CHUNK_SIZE);
91        // let chunk = self.chunks.entry(chunk_pos).or_default();
92        let mut created = false;
93        let chunk = self.chunks.entry(chunk_pos).or_insert_with(|| {
94            created = true;
95            self.events.notify(TilemapEvent::ChunkCreated(chunk_pos));
96            Chunk::default()
97        });
98        if let Some(current_tile) = chunk.array2d.get(local) {
99            if *current_tile != tile {
100                if !created {
101                    self.events.notify(TilemapEvent::ChunkChanged(chunk_pos));
102                }
103                chunk.array2d.set(local, tile);
104            }
105        }
106    }
107
108    pub fn remove_tile(&mut self, pos: V2i32) {
109        let (chunk_pos, local) = pos.modular_decompose(CHUNK_SIZE);
110        let chunk = self.chunks.entry(chunk_pos).or_default();
111        chunk.array2d.set(local, 0);
112        if chunk.array2d.data.iter().all(|x| x == &0) {
113            self.events.notify(TilemapEvent::ChunkRemoved(chunk_pos));
114            self.chunks.remove(&chunk_pos);
115        }
116    }
117
118    fn for_tiles_in_rect<F: FnMut(V2i32, u8)>(
119        &self,
120        rect: Rect2i32,
121        dir_x: f32,
122        dir_y: f32,
123        mut f: F,
124    ) {
125        let flip_x = dir_x < 0.0;
126        let flip_y = dir_y < 0.0;
127        if flip_y {
128            if flip_x {
129                for tile_pos in rect.iterate_south_west() {
130                    f(tile_pos, self.get_tile(tile_pos));
131                }
132            } else {
133                for tile_pos in rect.iterate_south_east() {
134                    f(tile_pos, self.get_tile(tile_pos));
135                }
136            }
137        } else if flip_x {
138            for tile_pos in rect.iterate_north_west() {
139                f(tile_pos, self.get_tile(tile_pos));
140            }
141        } else {
142            for tile_pos in rect.iterate_north_east() {
143                f(tile_pos, self.get_tile(tile_pos));
144            }
145        }
146    }
147
148    pub fn simple_resolve_ball(&self, col: &Ball, mut pos: Vec2, dir: &Vec2) -> Vec2 {
149        let bbox = col.symmetric_bounding_box();
150        let rect = self.quantize(&bbox, &pos);
151        self.for_tiles_in_rect(rect, dir.x, dir.y, |p, t| {
152            if t > 0 {
153                let b = Box2d::with_halfdims(0.5, 0.5);
154                let delta = self.tile_to_world_pos(&p) - pos;
155                if let Some(error) = col.penetrates_rel(&b, &delta) {
156                    pos += error;
157                }
158            }
159        });
160        pos
161    }
162
163    pub fn simple_resolve_box(&self, col: &Box2d, mut pos: Vec2, dir: &Vec2) -> Vec2 {
164        let rect = self.quantize(&col, &pos);
165        self.for_tiles_in_rect(rect, dir.x, dir.y, |p, t| {
166            if t > 0 {
167                let b = Box2d::with_halfdims(0.5, 0.5);
168                let delta = self.tile_to_world_pos(&p) - pos;
169                if let Some(error) = col.penetrates_rel(&b, &delta) {
170                    pos += error;
171                }
172            }
173        });
174        pos
175    }
176
177    // pub fn resolve(&self, character: &mut crate::character::Character) {
178    //     let rect = self.quantize(&character.col, &character.pos);
179    //     self.for_tiles_in_rect(rect, character.vel.x, character.vel.y, |p, t| {
180    //         if t > 0 {
181    //             let b = Box2d::with_halfdims(0.5, 0.5);
182    //             let delta = self.tile_to_world_pos(&p) - character.pos;
183    //             if let Some(error) = character.col.penetrates(&b, &delta) {
184    //                 character.pos -= error;
185    //             }
186    //         }
187    //     });
188    // }
189}
190
191impl CollidesRel2d<Point> for Tilemap {
192    fn collides_rel(&self, _t: &Point, rel: &impl Transformation2d) -> bool {
193        let delta = rel.apply_origin();
194        let tile_pos = self.world_to_tile_pos(&delta);
195        let tile = self.get_tile(tile_pos);
196        tile != 0
197    }
198}
199
200impl PenetratesRel2d<Point> for Tilemap {
201    fn penetrates_rel(&self, _t: &Point, rel: &impl Transformation2d) -> Option<Vec2> {
202        let delta = rel.apply_origin();
203        let tile_pos = self.world_to_tile_pos(&delta);
204        let tile = self.get_tile(tile_pos);
205        let up = self.get_tile(tile_pos + V2i32::Y);
206        todo!()
207    }
208}
209
210impl Tilemap {
211    pub fn save(&self, filename: &'static str) -> std::io::Result<()> {
212        let file = std::fs::File::create(filename)?;
213        let mut writer = BufWriter::new(file);
214        let serialized = serde_json::to_writer(&mut writer, self)?;
215        writer.flush()?;
216        Ok(())
217    }
218
219    pub fn load(filename: &'static str) -> std::io::Result<Self> {
220        let file = std::fs::File::open(filename)?;
221        let mut reader = BufReader::new(file);
222        Ok(serde_json::from_reader(&mut reader)?)
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use lk_math::vector::V2;
230
231    #[test]
232    fn serialize_v2() {
233        let v = V2::from_xy(3, 2);
234        let serialized = serde_json::to_string(&v).unwrap();
235        println!("{serialized}");
236        let deserialized: V2<i32> = serde_json::from_str(&serialized).unwrap();
237        assert_eq!(v, deserialized);
238    }
239
240    #[test]
241    fn serialize_chunk() {
242        let chunk = Chunk::default();
243        let serialized = serde_json::to_string(&chunk).unwrap();
244        println!("{serialized}");
245        let deserialized: Chunk = serde_json::from_str(&serialized).unwrap();
246        assert_eq!(chunk.array2d.dims, deserialized.array2d.dims);
247        assert_eq!(chunk.array2d.dim_strides, deserialized.array2d.dim_strides);
248        assert_eq!(chunk.array2d.data, deserialized.array2d.data);
249    }
250
251    #[test]
252    fn serialize_tilemap() {
253        let mut tilemap = Tilemap::default();
254        tilemap.set_tile(lk_math::vector::V2::from_xy(3, 2), 1);
255        let serialized = serde_json::to_string(&tilemap).unwrap();
256        println!("{serialized}");
257        let deserialized: Tilemap = serde_json::from_str(&serialized).unwrap();
258        assert_eq!(tilemap.chunks.len(), deserialized.chunks.len());
259    }
260}