1use binrw::{BinRead, BinWrite};
16
17#[derive(Debug, Clone, BinRead, BinWrite)]
48#[brw(little)]
49pub struct McshChunk {
50 #[br(count = 512)]
55 pub shadow_map: Vec<u8>,
56}
57
58impl Default for McshChunk {
59 fn default() -> Self {
60 Self {
61 shadow_map: vec![0; 512], }
63 }
64}
65
66impl McshChunk {
67 pub const RESOLUTION: usize = 64;
69
70 pub const SIZE_BYTES: usize = 512;
72
73 pub fn is_shadowed(&self, x: usize, y: usize) -> bool {
84 if x >= Self::RESOLUTION || y >= Self::RESOLUTION {
85 return false;
86 }
87
88 let byte_index = y * 8 + (x / 8);
89 let bit_index = x % 8;
90
91 if let Some(&byte) = self.shadow_map.get(byte_index) {
92 (byte >> bit_index) & 1 != 0
93 } else {
94 false
95 }
96 }
97
98 pub fn set_shadow(&mut self, x: usize, y: usize, shadowed: bool) {
106 if x >= Self::RESOLUTION || y >= Self::RESOLUTION {
107 return;
108 }
109
110 let byte_index = y * 8 + (x / 8);
111 let bit_index = x % 8;
112
113 if let Some(byte) = self.shadow_map.get_mut(byte_index) {
114 if shadowed {
115 *byte |= 1 << bit_index;
116 } else {
117 *byte &= !(1 << bit_index);
118 }
119 }
120 }
121
122 pub fn shadowed_count(&self) -> usize {
124 self.shadow_map
125 .iter()
126 .map(|&byte| byte.count_ones() as usize)
127 .sum()
128 }
129
130 pub fn shadow_ratio(&self) -> f32 {
132 let total_texels = Self::RESOLUTION * Self::RESOLUTION;
133 self.shadowed_count() as f32 / total_texels as f32
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use std::io::Cursor;
141
142 #[test]
143 fn test_mcsh_chunk_size() {
144 let chunk = McshChunk::default();
145 assert_eq!(chunk.shadow_map.len(), 512);
146 assert_eq!(McshChunk::SIZE_BYTES, 512);
147 assert_eq!(McshChunk::RESOLUTION, 64);
148 }
149
150 #[test]
151 fn test_mcsh_parse_512_bytes() {
152 let data = vec![0u8; 512];
153 let mut cursor = Cursor::new(data);
154 let chunk = McshChunk::read_le(&mut cursor).unwrap();
155
156 assert_eq!(chunk.shadow_map.len(), 512);
157 assert_eq!(chunk.shadowed_count(), 0);
158 }
159
160 #[test]
161 fn test_mcsh_default_all_unshadowed() {
162 let chunk = McshChunk::default();
163
164 for y in 0..64 {
165 for x in 0..64 {
166 assert!(!chunk.is_shadowed(x, y));
167 }
168 }
169
170 assert_eq!(chunk.shadowed_count(), 0);
171 assert_eq!(chunk.shadow_ratio(), 0.0);
172 }
173
174 #[test]
175 fn test_mcsh_shadow_bit_access() {
176 let mut chunk = McshChunk::default();
177
178 chunk.set_shadow(0, 0, true);
180 chunk.set_shadow(7, 0, true);
181 chunk.set_shadow(31, 15, true);
182 chunk.set_shadow(63, 63, true);
183
184 assert!(chunk.is_shadowed(0, 0));
185 assert!(chunk.is_shadowed(7, 0));
186 assert!(chunk.is_shadowed(31, 15));
187 assert!(chunk.is_shadowed(63, 63));
188
189 assert!(!chunk.is_shadowed(1, 0));
190 assert!(!chunk.is_shadowed(0, 1));
191
192 chunk.set_shadow(0, 0, false);
194 assert!(!chunk.is_shadowed(0, 0));
195 }
196
197 #[test]
198 fn test_mcsh_lsb_first_bit_ordering() {
199 let mut chunk = McshChunk::default();
200
201 for x in 0..8 {
203 chunk.set_shadow(x, 0, true);
204 }
205
206 assert_eq!(chunk.shadow_map[0], 0xFF);
208
209 chunk.set_shadow(0, 1, true); chunk.set_shadow(2, 1, true); chunk.set_shadow(4, 1, true); chunk.set_shadow(6, 1, true); assert_eq!(chunk.shadow_map[8], 0b0101_0101);
217
218 assert!(chunk.is_shadowed(0, 1));
220 assert!(!chunk.is_shadowed(1, 1));
221 assert!(chunk.is_shadowed(2, 1));
222 assert!(!chunk.is_shadowed(3, 1));
223 }
224
225 #[test]
226 fn test_mcsh_bounds_checking() {
227 let mut chunk = McshChunk::default();
228
229 assert!(!chunk.is_shadowed(64, 0));
231 assert!(!chunk.is_shadowed(0, 64));
232 assert!(!chunk.is_shadowed(100, 100));
233
234 chunk.set_shadow(64, 0, true);
236 chunk.set_shadow(0, 64, true);
237 chunk.set_shadow(100, 100, true);
238
239 assert_eq!(chunk.shadowed_count(), 0);
241 }
242
243 #[test]
244 fn test_mcsh_shadow_count() {
245 let mut chunk = McshChunk::default();
246
247 assert_eq!(chunk.shadowed_count(), 0);
248
249 chunk.set_shadow(0, 0, true);
251 chunk.set_shadow(10, 5, true);
252 chunk.set_shadow(20, 10, true);
253 chunk.set_shadow(30, 15, true);
254 chunk.set_shadow(40, 20, true);
255 chunk.set_shadow(50, 25, true);
256 chunk.set_shadow(15, 30, true);
257 chunk.set_shadow(25, 35, true);
258 chunk.set_shadow(35, 40, true);
259 chunk.set_shadow(45, 45, true);
260
261 assert_eq!(chunk.shadowed_count(), 10);
262 }
263
264 #[test]
265 fn test_mcsh_shadow_ratio() {
266 let mut chunk = McshChunk::default();
267
268 assert_eq!(chunk.shadow_ratio(), 0.0);
270
271 for x in 0..64 {
273 chunk.set_shadow(x, 0, true);
274 }
275
276 let expected_ratio = 64.0 / 4096.0; assert!((chunk.shadow_ratio() - expected_ratio).abs() < 0.0001);
278
279 for y in 0..64 {
281 for x in 0..64 {
282 chunk.set_shadow(x, y, true);
283 }
284 }
285
286 assert_eq!(chunk.shadow_ratio(), 1.0);
287 assert_eq!(chunk.shadowed_count(), 4096);
288 }
289
290 #[test]
291 fn test_mcsh_round_trip() {
292 let mut original = McshChunk::default();
293
294 for i in 0..64 {
296 original.set_shadow(i, i, true);
297 }
298
299 assert_eq!(original.shadowed_count(), 64);
300
301 let mut buffer = Cursor::new(Vec::new());
303 original.write_le(&mut buffer).unwrap();
304
305 let data = buffer.into_inner();
306 assert_eq!(data.len(), 512);
307
308 let mut cursor = Cursor::new(data);
310 let parsed = McshChunk::read_le(&mut cursor).unwrap();
311
312 assert_eq!(parsed.shadowed_count(), 64);
314 for i in 0..64 {
315 assert!(parsed.is_shadowed(i, i));
316 }
317 }
318
319 #[test]
320 fn test_mcsh_byte_indexing() {
321 let mut chunk = McshChunk::default();
322
323 chunk.set_shadow(0, 0, true);
325 assert_eq!(chunk.shadow_map[0], 0b0000_0001);
326
327 chunk.set_shadow(7, 0, true);
328 assert_eq!(chunk.shadow_map[0], 0b1000_0001);
329
330 chunk.set_shadow(8, 0, true);
332 assert_eq!(chunk.shadow_map[1], 0b0000_0001);
333
334 chunk.set_shadow(0, 1, true);
336 assert_eq!(chunk.shadow_map[8], 0b0000_0001);
337
338 chunk.set_shadow(63, 63, true);
340 assert_eq!(chunk.shadow_map[511], 0b1000_0000);
341 }
342
343 #[test]
344 fn test_mcsh_all_bytes_used() {
345 let mut chunk = McshChunk::default();
346
347 for byte_index in 0..512 {
349 let y = byte_index / 8;
350 let x = (byte_index % 8) * 8;
351 chunk.set_shadow(x, y, true);
352 }
353
354 for byte_index in 0..512 {
356 assert_eq!(chunk.shadow_map[byte_index] & 1, 1);
357 }
358
359 assert_eq!(chunk.shadowed_count(), 512);
360 }
361
362 #[test]
363 fn test_mcsh_full_coverage() {
364 let mut chunk = McshChunk::default();
365
366 for y in 0..64 {
368 for x in 0..64 {
369 chunk.set_shadow(x, y, true);
370 }
371 }
372
373 for &byte in &chunk.shadow_map {
375 assert_eq!(byte, 0xFF);
376 }
377
378 assert_eq!(chunk.shadowed_count(), 4096);
379 assert_eq!(chunk.shadow_ratio(), 1.0);
380 }
381}