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 #[serde_as(as = "Vec<(_, _)>")]
33 chunks: std::collections::HashMap<V2i32, Chunk>,
34 #[serde(skip)]
35 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 }
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 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 }
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}