veloren_common_net/msg/
compression.rs

1use common::{
2    terrain::{Block, BlockKind, chonk::Chonk},
3    vol::{BaseVol, ReadVol, RectVolSize, WriteVol},
4    volumes::vol_grid_2d::VolGrid2d,
5};
6use hashbrown::HashMap;
7use image::{ImageBuffer, ImageDecoder, ImageEncoder, Pixel};
8use num_traits::cast::FromPrimitive;
9use serde::{Deserialize, Serialize};
10use std::{
11    fmt::Debug,
12    io::{Cursor, Read, Write},
13    marker::PhantomData,
14};
15use tracing::warn;
16use vek::*;
17
18/// Wrapper for compressed, serialized data (for stuff that doesn't use the
19/// default lz4 compression)
20#[derive(Clone, Debug, Serialize, Deserialize)]
21pub struct CompressedData<T> {
22    pub data: Vec<u8>,
23    compressed: bool,
24    _phantom: PhantomData<T>,
25}
26
27impl<T: Serialize> CompressedData<T> {
28    pub fn compress(t: &T, level: u32) -> Self {
29        use flate2::{Compression, write::DeflateEncoder};
30        let uncompressed = bincode::serialize(t)
31            .expect("bincode serialization can only fail if a byte limit is set");
32
33        if uncompressed.len() >= 32 {
34            const EXPECT_MSG: &str =
35                "compression only fails for fallible Read/Write impls (which Vec<u8> is not)";
36
37            let buf = Vec::with_capacity(uncompressed.len() / 10);
38            let mut encoder = DeflateEncoder::new(buf, Compression::new(level));
39            encoder.write_all(&uncompressed).expect(EXPECT_MSG);
40            let compressed = encoder.finish().expect(EXPECT_MSG);
41            CompressedData {
42                data: compressed,
43                compressed: true,
44                _phantom: PhantomData,
45            }
46        } else {
47            CompressedData {
48                data: uncompressed,
49                compressed: false,
50                _phantom: PhantomData,
51            }
52        }
53    }
54}
55
56impl<T: for<'a> Deserialize<'a>> CompressedData<T> {
57    pub fn decompress(&self) -> Option<T> {
58        if self.compressed {
59            let mut uncompressed = Vec::with_capacity(self.data.len());
60            flate2::read::DeflateDecoder::new(&*self.data)
61                .read_to_end(&mut uncompressed)
62                .ok()?;
63            bincode::deserialize(&uncompressed).ok()
64        } else {
65            bincode::deserialize(&self.data).ok()
66        }
67    }
68}
69
70/// Formula for packing voxel data into a 2d array
71pub trait PackingFormula: Copy {
72    fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32);
73    fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32);
74}
75
76/// A wide, short image. Shares the advantage of not wasting space with
77/// TallPacking (which is strictly worse, and was moved to benchmark code in
78/// `world`), but faster to compress and smaller since PNG compresses each
79/// row independently, so a wide image has fewer calls to the compressor. FLIP_X
80/// has the same spatial locality preserving behavior as with TallPacking.
81#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
82pub struct WidePacking<const FLIP_X: bool>();
83
84impl<const FLIP_X: bool> PackingFormula for WidePacking<FLIP_X> {
85    #[inline(always)]
86    fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32) { (dims.x * dims.z, dims.y) }
87
88    #[inline(always)]
89    fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32) {
90        let i0 = if FLIP_X {
91            if z % 2 == 0 { x } else { dims.x - x - 1 }
92        } else {
93            x
94        };
95        let i = z * dims.x + i0;
96        let j = y;
97        (i, j)
98    }
99}
100
101/// A grid of the z levels, left to right, top to bottom, like English prose.
102/// Convenient for visualizing terrain for debugging or for user-inspectable
103/// file formats, but wastes space if the number of z levels isn't a perfect
104/// square.
105#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
106pub struct GridLtrPacking;
107
108impl PackingFormula for GridLtrPacking {
109    #[inline(always)]
110    fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32) {
111        let rootz = (dims.z as f64).sqrt().ceil() as u32;
112        (dims.x * rootz, dims.y * rootz)
113    }
114
115    #[inline(always)]
116    fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32) {
117        let rootz = (dims.z as f64).sqrt().ceil() as u32;
118        let i = x + (z % rootz) * dims.x;
119        let j = y + (z / rootz) * dims.y;
120        (i, j)
121    }
122}
123
124pub trait VoxelImageEncoding {
125    type Workspace;
126    type Output;
127    fn create(width: u32, height: u32) -> Self::Workspace;
128    fn put_solid(&self, ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>);
129    fn put_sprite(
130        &self,
131        ws: &mut Self::Workspace,
132        x: u32,
133        y: u32,
134        kind: BlockKind,
135        sprite_data: [u8; 3],
136    );
137    fn finish(ws: &Self::Workspace) -> Option<Self::Output>;
138}
139
140pub trait VoxelImageDecoding: VoxelImageEncoding {
141    fn start(ws: &Self::Output) -> Option<Self::Workspace>;
142    fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block;
143}
144
145pub fn image_from_bytes<I: ImageDecoder, P: 'static + Pixel<Subpixel = u8>>(
146    decoder: I,
147) -> Option<ImageBuffer<P, Vec<u8>>> {
148    let (w, h) = decoder.dimensions();
149    let mut buf = vec![0; decoder.total_bytes() as usize];
150    decoder.read_image(&mut buf).ok()?;
151    ImageBuffer::from_raw(w, h, buf)
152}
153
154impl<VIE: VoxelImageEncoding> VoxelImageEncoding for &VIE {
155    type Output = VIE::Output;
156    type Workspace = VIE::Workspace;
157
158    fn create(width: u32, height: u32) -> Self::Workspace { VIE::create(width, height) }
159
160    fn put_solid(&self, ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) {
161        (*self).put_solid(ws, x, y, kind, rgb)
162    }
163
164    fn put_sprite(
165        &self,
166        ws: &mut Self::Workspace,
167        x: u32,
168        y: u32,
169        kind: BlockKind,
170        sprite_data: [u8; 3],
171    ) {
172        (*self).put_sprite(ws, x, y, kind, sprite_data)
173    }
174
175    fn finish(ws: &Self::Workspace) -> Option<Self::Output> { VIE::finish(ws) }
176}
177
178impl<VIE: VoxelImageDecoding> VoxelImageDecoding for &VIE {
179    fn start(ws: &Self::Output) -> Option<Self::Workspace> { VIE::start(ws) }
180
181    fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block {
182        VIE::get_block(ws, x, y, is_border)
183    }
184}
185
186#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
187pub struct QuadPngEncoding<const RESOLUTION_DIVIDER: u32>();
188
189impl<const N: u32> VoxelImageEncoding for QuadPngEncoding<N> {
190    type Output = CompressedData<(Vec<u8>, [usize; 3], Vec<[u8; 3]>)>;
191    type Workspace = (
192        ImageBuffer<image::Luma<u8>, Vec<u8>>,
193        ImageBuffer<image::Luma<u8>, Vec<u8>>,
194        ImageBuffer<image::Luma<u8>, Vec<u8>>,
195        ImageBuffer<image::Rgb<u8>, Vec<u8>>,
196        Vec<[u8; 3]>,
197        HashMap<[u8; 3], u16>,
198    );
199
200    fn create(width: u32, height: u32) -> Self::Workspace {
201        (
202            ImageBuffer::new(width, height),
203            ImageBuffer::new(width, height),
204            ImageBuffer::new(width, height),
205            ImageBuffer::new(width / N, height / N),
206            Vec::new(),
207            HashMap::new(),
208        )
209    }
210
211    #[inline(always)]
212    fn put_solid(&self, ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) {
213        ws.0.put_pixel(x, y, image::Luma([kind as u8]));
214        ws.3.put_pixel(x / N, y / N, image::Rgb([rgb.r, rgb.g, rgb.b]));
215    }
216
217    #[inline(always)]
218    fn put_sprite(
219        &self,
220        ws: &mut Self::Workspace,
221        x: u32,
222        y: u32,
223        kind: BlockKind,
224        sprite_data: [u8; 3],
225    ) {
226        let index = ws.5.entry(sprite_data).or_insert_with(|| {
227            let index =
228                ws.4.len()
229                    .try_into()
230                    .expect("Cannot have more than 2^16 unique sprites in one chunk");
231            ws.4.push(sprite_data);
232            index
233        });
234
235        let index = index.to_be_bytes();
236
237        ws.0.put_pixel(x, y, image::Luma([kind as u8]));
238        ws.1.put_pixel(x, y, image::Luma([index[0]]));
239        ws.2.put_pixel(x, y, image::Luma([index[1]]));
240    }
241
242    fn finish(ws: &Self::Workspace) -> Option<Self::Output> {
243        let mut buf = Vec::new();
244        use image::codecs::png::{CompressionType, FilterType};
245        let mut indices = [0; 3];
246        let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| {
247            let png = image::codecs::png::PngEncoder::new_with_quality(
248                &mut buf,
249                CompressionType::Fast,
250                FilterType::Up,
251            );
252            png.write_image(
253                x.as_raw(),
254                x.width(),
255                x.height(),
256                image::ExtendedColorType::L8,
257            )
258            .ok()?;
259            indices[i] = buf.len();
260            Some(())
261        };
262        f(&ws.0, 0)?;
263        f(&ws.1, 1)?;
264        f(&ws.2, 2)?;
265
266        {
267            let png = image::codecs::png::PngEncoder::new_with_quality(
268                &mut buf,
269                CompressionType::Fast,
270                FilterType::Sub,
271            );
272            png.write_image(
273                ws.3.as_raw(),
274                ws.3.width(),
275                ws.3.height(),
276                image::ExtendedColorType::Rgb8,
277            )
278            .ok()?;
279        }
280
281        Some(CompressedData::compress(&(buf, indices, ws.4.clone()), 4))
282    }
283}
284
285/// AldanTanneo's sin approximation (since std's sin implementation isn't const
286/// yet)
287const fn sin(x: f64) -> f64 {
288    use std::f64::consts::PI;
289    let mut x = (x - PI * 0.5) % (PI * 2.0);
290    x = if x < 0.0 { -x } else { x } - PI;
291    x = if x < 0.0 { -x } else { x } - PI * 0.5;
292
293    let x2 = x * x;
294    let x3 = x * x2 / 6.0;
295    let x5 = x3 * x2 / 20.0;
296    let x7 = x5 * x2 / 42.0;
297    let x9 = x7 * x2 / 72.0;
298    let x11 = x9 * x2 / 110.0;
299    x - x3 + x5 - x7 + x9 - x11
300}
301
302/// https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel
303const fn lanczos(x: f64, a: f64) -> f64 {
304    use std::f64::consts::PI;
305    if x < f64::EPSILON {
306        1.0
307    } else if -a <= x && x <= a {
308        (a * sin(PI * x) * sin(PI * x / a)) / (PI * PI * x * x)
309    } else {
310        0.0
311    }
312}
313
314/// Needs to be a separate function since `const fn`s can appear in the output
315/// of a const-generic function, but raw arithmetic expressions can't be
316const fn lanczos_lookup_array_size(n: u32, r: u32) -> usize { (2 * n * (r + 1) - 1) as usize }
317
318const fn gen_lanczos_lookup<const N: u32, const R: u32>(
319    a: f64,
320) -> [f64; lanczos_lookup_array_size(N, R)] {
321    let quadpng_n = N as f64;
322    let sample_radius = R as f64;
323
324    let step = 1.0 / (2.0 * quadpng_n);
325    let max = (quadpng_n - 1.0) / (2.0 * quadpng_n) + sample_radius;
326    // after doing some maths with the above:
327    let mut array = [0.0; lanczos_lookup_array_size(N, R)];
328
329    let mut i = 0;
330    while i < array.len() {
331        array[i] = lanczos(step * i as f64 - max, a);
332        i += 1;
333    }
334    array
335}
336
337impl<const N: u32> VoxelImageDecoding for QuadPngEncoding<N> {
338    fn start(data: &Self::Output) -> Option<Self::Workspace> {
339        use image::codecs::png::PngDecoder;
340        let (quad, indices, sprite_data) = data.decompress()?;
341        let ranges: [_; 4] = [
342            0..indices[0],
343            indices[0]..indices[1],
344            indices[1]..indices[2],
345            indices[2]..quad.len(),
346        ];
347        let a = image_from_bytes(PngDecoder::new(Cursor::new(&quad[ranges[0].clone()])).ok()?)?;
348        let b = image_from_bytes(PngDecoder::new(Cursor::new(&quad[ranges[1].clone()])).ok()?)?;
349        let c = image_from_bytes(PngDecoder::new(Cursor::new(&quad[ranges[2].clone()])).ok()?)?;
350        let d = image_from_bytes(PngDecoder::new(Cursor::new(&quad[ranges[3].clone()])).ok()?)?;
351        Some((a, b, c, d, sprite_data, HashMap::new()))
352    }
353
354    fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block {
355        if let Some(kind) = BlockKind::from_u8(ws.0.get_pixel(x, y).0[0]) {
356            if kind.is_filled() {
357                let (w, h) = ws.3.dimensions();
358                let mut rgb = match 0 {
359                    // Weighted-average interpolation
360                    0 => {
361                        const SAMPLE_RADIUS: i32 = 2i32; // sample_size = SAMPLE_RADIUS * 2 + 1
362                        let mut rgb: Vec3<f64> = Vec3::zero();
363                        let mut total = 0.0;
364                        for dx in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
365                            for dy in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
366                                let (i, j) = (
367                                    (x.wrapping_add(dx as u32) / N),
368                                    (y.wrapping_add(dy as u32) / N),
369                                );
370                                if i < w && j < h {
371                                    let r = 5.0 - (dx.abs() + dy.abs()) as f64;
372                                    let pix = Vec3::<u8>::from(ws.3.get_pixel(i, j).0);
373                                    if pix != Vec3::zero() {
374                                        rgb += r * pix.as_();
375                                        total += r;
376                                    }
377                                }
378                            }
379                        }
380                        rgb /= total;
381                        rgb
382                    },
383                    // Mckol's Lanczos interpolation with AldanTanneo's Lanczos LUT
384                    1 if N == 4 => {
385                        const LANCZOS_A: f64 = 2.0; // See https://www.desmos.com/calculator/xxejcymyua
386                        const SAMPLE_RADIUS: i32 = 2i32; // sample_size = SAMPLE_RADIUS * 2 + 1
387                        // rustc currently doesn't support supplying N and SAMPLE_RADIUS, even with
388                        // a few workarounds, so hack around it by using the dynamic check above
389                        const LANCZOS_LUT: [f64; lanczos_lookup_array_size(4, 2)] =
390                            gen_lanczos_lookup::<4, 2>(LANCZOS_A);
391
392                        // As a reminder: x, y are destination pixel coordinates (not downscaled).
393                        let mut rgb: Vec3<f64> = Vec3::zero();
394                        for dx in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
395                            for dy in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
396                                // Source pixel coordinates (downscaled):
397                                let (src_x, src_y) = (
398                                    (x.wrapping_add(dx as u32) / N),
399                                    (y.wrapping_add(dy as u32) / N),
400                                );
401                                if src_x < w && src_y < h {
402                                    let pix: Vec3<f64> =
403                                        Vec3::<u8>::from(ws.3.get_pixel(src_x, src_y).0).as_();
404                                    // Relative coordinates where 1 unit is the size of one source
405                                    // pixel and 0 is the center of the source pixel:
406                                    let x_rel = ((x % N) as f64 - (N - 1) as f64 / 2.0) / N as f64;
407                                    let y_rel = ((y % N) as f64 - (N - 1) as f64 / 2.0) / N as f64;
408                                    // Distance from the currently processed target pixel's center
409                                    // to the currently processed source pixel's center:
410                                    rgb += LANCZOS_LUT
411                                        .get((dx as f64 - x_rel).abs() as usize)
412                                        .unwrap_or(&0.0)
413                                        * LANCZOS_LUT
414                                            .get((dy as f64 - y_rel).abs() as usize)
415                                            .unwrap_or(&0.0)
416                                        * pix;
417                                }
418                            }
419                        }
420                        rgb
421                    },
422                    // Mckol's Lanczos interpolation
423                    1 | 2 => {
424                        const LANCZOS_A: f64 = 2.0; // See https://www.desmos.com/calculator/xxejcymyua
425                        const SAMPLE_RADIUS: i32 = 2i32; // sample_size = SAMPLE_RADIUS * 2 + 1
426                        // As a reminder: x, y are destination pixel coordinates (not downscaled).
427                        let mut rgb: Vec3<f64> = Vec3::zero();
428                        for dx in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
429                            for dy in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
430                                // Source pixel coordinates (downscaled):
431                                let (src_x, src_y) = (
432                                    (x.wrapping_add(dx as u32) / N),
433                                    (y.wrapping_add(dy as u32) / N),
434                                );
435                                if src_x < w && src_y < h {
436                                    let pix: Vec3<f64> =
437                                        Vec3::<u8>::from(ws.3.get_pixel(src_x, src_y).0).as_();
438                                    // Relative coordinates where 1 unit is the size of one source
439                                    // pixel and 0 is the center of the source pixel:
440                                    let x_rel = ((x % N) as f64 - (N - 1) as f64 / 2.0) / N as f64;
441                                    let y_rel = ((y % N) as f64 - (N - 1) as f64 / 2.0) / N as f64;
442                                    // Distance from the currently processed target pixel's center
443                                    // to the currently processed source pixel's center:
444                                    rgb += lanczos((dx as f64 - x_rel).abs(), LANCZOS_A)
445                                        * lanczos((dy as f64 - y_rel).abs(), LANCZOS_A)
446                                        * pix;
447                                }
448                            }
449                        }
450                        rgb
451                    },
452                    // No interpolation
453                    _ => Vec3::<u8>::from(ws.3.get_pixel(x / N, y / N).0).as_(),
454                };
455                if is_border {
456                    rgb = Vec3::<u8>::from(ws.3.get_pixel(x / N, y / N).0).as_();
457                }
458                Block::new(kind, Rgb {
459                    r: rgb.x as u8,
460                    g: rgb.y as u8,
461                    b: rgb.z as u8,
462                })
463            } else {
464                let index =
465                    u16::from_be_bytes([ws.1.get_pixel(x, y).0[0], ws.2.get_pixel(x, y).0[0]]);
466                Block::from_raw(kind, ws.4[index as usize])
467            }
468        } else {
469            Block::empty()
470        }
471    }
472}
473
474#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
475pub struct TriPngEncoding<const AVERAGE_PALETTE: bool>();
476
477impl<const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<AVERAGE_PALETTE> {
478    type Output = CompressedData<(Vec<u8>, Vec<Rgb<u8>>, [usize; 3], Vec<[u8; 3]>)>;
479    type Workspace = (
480        ImageBuffer<image::Luma<u8>, Vec<u8>>,
481        ImageBuffer<image::Luma<u8>, Vec<u8>>,
482        ImageBuffer<image::Luma<u8>, Vec<u8>>,
483        HashMap<BlockKind, HashMap<Rgb<u8>, usize>>,
484        Vec<[u8; 3]>,
485        HashMap<[u8; 3], u16>,
486    );
487
488    fn create(width: u32, height: u32) -> Self::Workspace {
489        (
490            ImageBuffer::new(width, height),
491            ImageBuffer::new(width, height),
492            ImageBuffer::new(width, height),
493            HashMap::new(),
494            Vec::new(),
495            HashMap::new(),
496        )
497    }
498
499    fn put_solid(&self, ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) {
500        ws.0.put_pixel(x, y, image::Luma([kind as u8]));
501        ws.1.put_pixel(x, y, image::Luma([0]));
502        ws.2.put_pixel(x, y, image::Luma([0]));
503        if AVERAGE_PALETTE {
504            *ws.3.entry(kind).or_default().entry(rgb).or_insert(0) += 1;
505        }
506    }
507
508    fn put_sprite(
509        &self,
510        ws: &mut Self::Workspace,
511        x: u32,
512        y: u32,
513        kind: BlockKind,
514        sprite_data: [u8; 3],
515    ) {
516        let index = ws.5.entry(sprite_data).or_insert_with(|| {
517            let index =
518                ws.4.len()
519                    .try_into()
520                    .expect("Cannot have more than 2^16 sprites in one chunk");
521            ws.4.push(sprite_data);
522            index
523        });
524        let index = index.to_be_bytes();
525
526        ws.0.put_pixel(x, y, image::Luma([kind as u8]));
527        ws.1.put_pixel(x, y, image::Luma([index[0]]));
528        ws.2.put_pixel(x, y, image::Luma([index[1]]));
529    }
530
531    fn finish(ws: &Self::Workspace) -> Option<Self::Output> {
532        let mut buf = Vec::new();
533        use image::codecs::png::{CompressionType, FilterType};
534        let mut indices = [0; 3];
535        let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| {
536            let png = image::codecs::png::PngEncoder::new_with_quality(
537                &mut buf,
538                CompressionType::Fast,
539                FilterType::Up,
540            );
541            png.write_image(
542                x.as_raw(),
543                x.width(),
544                x.height(),
545                image::ExtendedColorType::L8,
546            )
547            .ok()?;
548            indices[i] = buf.len();
549            Some(())
550        };
551        f(&ws.0, 0)?;
552        f(&ws.1, 1)?;
553        f(&ws.2, 2)?;
554
555        let palette = if AVERAGE_PALETTE {
556            let mut palette = vec![Rgb { r: 0, g: 0, b: 0 }; 256];
557            for (block, hist) in ws.3.iter() {
558                let (mut r, mut g, mut b) = (0.0, 0.0, 0.0);
559                let mut total = 0;
560                for (color, count) in hist.iter() {
561                    r += color.r as f64 * *count as f64;
562                    g += color.g as f64 * *count as f64;
563                    b += color.b as f64 * *count as f64;
564                    total += *count;
565                }
566                r /= total as f64;
567                g /= total as f64;
568                b /= total as f64;
569                palette[*block as u8 as usize].r = r as u8;
570                palette[*block as u8 as usize].g = g as u8;
571                palette[*block as u8 as usize].b = b as u8;
572            }
573            palette
574        } else {
575            Vec::new()
576        };
577
578        Some(CompressedData::compress(
579            &(buf, palette, indices, ws.4.clone()),
580            4,
581        ))
582    }
583}
584
585impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<AVERAGE_PALETTE> {
586    fn start(data: &Self::Output) -> Option<Self::Workspace> {
587        use image::codecs::png::PngDecoder;
588        let (quad, palette, indices, sprite_data) = data.decompress()?;
589        let ranges: [_; 3] = [
590            0..indices[0],
591            indices[0]..indices[1],
592            indices[1]..indices[2],
593        ];
594        let a = image_from_bytes(PngDecoder::new(Cursor::new(&quad[ranges[0].clone()])).ok()?)?;
595        let b = image_from_bytes(PngDecoder::new(Cursor::new(&quad[ranges[1].clone()])).ok()?)?;
596        let c = image_from_bytes(PngDecoder::new(Cursor::new(&quad[ranges[2].clone()])).ok()?)?;
597        let mut d: HashMap<_, HashMap<_, _>> = HashMap::new();
598        if AVERAGE_PALETTE {
599            for i in 0..=255 {
600                if let Some(block) = BlockKind::from_u8(i) {
601                    d.entry(block)
602                        .or_default()
603                        .entry(palette[i as usize])
604                        .insert(1);
605                }
606            }
607        }
608
609        Some((a, b, c, d, sprite_data, HashMap::new()))
610    }
611
612    fn get_block(ws: &Self::Workspace, x: u32, y: u32, _: bool) -> Block {
613        if let Some(kind) = BlockKind::from_u8(ws.0.get_pixel(x, y).0[0]) {
614            if kind.is_filled() {
615                let rgb = if AVERAGE_PALETTE {
616                    *ws.3
617                        .get(&kind)
618                        .and_then(|h| h.keys().next())
619                        .unwrap_or(&Rgb::default())
620                } else {
621                    use BlockKind::*;
622                    match kind {
623                        Air | Water | Lava => Rgb { r: 0, g: 0, b: 0 },
624                        Rock => Rgb {
625                            r: 93,
626                            g: 110,
627                            b: 145,
628                        },
629                        WeakRock => Rgb {
630                            r: 93,
631                            g: 132,
632                            b: 145,
633                        },
634                        GlowingRock => Rgb {
635                            r: 61,
636                            g: 229,
637                            b: 198,
638                        },
639                        GlowingWeakRock => Rgb {
640                            r: 61,
641                            g: 185,
642                            b: 240,
643                        },
644                        Grass => Rgb {
645                            r: 51,
646                            g: 160,
647                            b: 94,
648                        },
649                        Snow => Rgb {
650                            r: 192,
651                            g: 255,
652                            b: 255,
653                        },
654                        ArtSnow => Rgb {
655                            r: 192,
656                            g: 255,
657                            b: 255,
658                        },
659                        Ice => Rgb {
660                            r: 150,
661                            g: 190,
662                            b: 255,
663                        },
664                        Earth => Rgb {
665                            r: 200,
666                            g: 140,
667                            b: 93,
668                        },
669                        Sand => Rgb {
670                            r: 241,
671                            g: 177,
672                            b: 128,
673                        },
674                        Wood => Rgb {
675                            r: 128,
676                            g: 77,
677                            b: 51,
678                        },
679                        Leaves => Rgb {
680                            r: 93,
681                            g: 206,
682                            b: 64,
683                        },
684                        ArtLeaves => Rgb {
685                            r: 93,
686                            g: 206,
687                            b: 64,
688                        },
689                        GlowingMushroom => Rgb {
690                            r: 50,
691                            g: 250,
692                            b: 250,
693                        },
694                        Misc => Rgb {
695                            r: 255,
696                            g: 0,
697                            b: 255,
698                        },
699                    }
700                };
701                Block::new(kind, rgb)
702            } else {
703                let index =
704                    u16::from_be_bytes([ws.1.get_pixel(x, y).0[0], ws.2.get_pixel(x, y).0[0]]);
705                Block::from_raw(kind, ws.4[index as usize])
706            }
707        } else {
708            Block::empty()
709        }
710    }
711}
712
713pub fn image_terrain_chonk<S: RectVolSize, M: Clone, P: PackingFormula, VIE: VoxelImageEncoding>(
714    vie: &VIE,
715    packing: P,
716    chonk: &Chonk<Block, S, M>,
717) -> Option<VIE::Output> {
718    image_terrain(
719        vie,
720        packing,
721        chonk,
722        Vec3::new(0, 0, chonk.get_min_z() as u32),
723        Vec3::new(S::RECT_SIZE.x, S::RECT_SIZE.y, chonk.get_max_z() as u32),
724    )
725}
726
727pub fn image_terrain_volgrid<
728    S: RectVolSize + Debug,
729    M: Clone + Debug,
730    P: PackingFormula,
731    VIE: VoxelImageEncoding,
732>(
733    vie: &VIE,
734    packing: P,
735    volgrid: &VolGrid2d<Chonk<Block, S, M>>,
736) -> Option<VIE::Output> {
737    let mut lo = Vec3::broadcast(i32::MAX);
738    let mut hi = Vec3::broadcast(i32::MIN);
739    for (pos, chonk) in volgrid.iter() {
740        lo.x = lo.x.min(pos.x * S::RECT_SIZE.x as i32);
741        lo.y = lo.y.min(pos.y * S::RECT_SIZE.y as i32);
742        lo.z = lo.z.min(chonk.get_min_z());
743
744        hi.x = hi.x.max((pos.x + 1) * S::RECT_SIZE.x as i32);
745        hi.y = hi.y.max((pos.y + 1) * S::RECT_SIZE.y as i32);
746        hi.z = hi.z.max(chonk.get_max_z());
747    }
748
749    image_terrain(vie, packing, volgrid, lo.as_(), hi.as_())
750}
751
752pub fn image_terrain<
753    V: BaseVol<Vox = Block> + ReadVol,
754    P: PackingFormula,
755    VIE: VoxelImageEncoding,
756>(
757    vie: &VIE,
758    packing: P,
759    vol: &V,
760    lo: Vec3<u32>,
761    hi: Vec3<u32>,
762) -> Option<VIE::Output> {
763    let dims = Vec3::new(
764        hi.x.wrapping_sub(lo.x),
765        hi.y.wrapping_sub(lo.y),
766        hi.z.wrapping_sub(lo.z),
767    );
768
769    let (width, height) = packing.dimensions(dims);
770    let mut image = VIE::create(width, height);
771    for z in 0..dims.z {
772        for y in 0..dims.y {
773            for x in 0..dims.x {
774                let (i, j) = packing.index(dims, x, y, z);
775
776                let block = *vol
777                    .get(
778                        Vec3::new(
779                            x.wrapping_add(lo.x),
780                            y.wrapping_add(lo.y),
781                            z.wrapping_add(lo.z),
782                        )
783                        .as_(),
784                    )
785                    .unwrap_or(&Block::empty());
786                match (block.get_color(), block.get_sprite()) {
787                    (Some(rgb), None) => {
788                        VIE::put_solid(vie, &mut image, i, j, *block, rgb);
789                    },
790                    (None, Some(_)) => {
791                        let data = block.to_u32().to_le_bytes();
792
793                        VIE::put_sprite(vie, &mut image, i, j, *block, [data[1], data[2], data[3]]);
794                    },
795                    _ => panic!(
796                        "attr being used for color vs sprite is mutually exclusive (and that's \
797                         required for this translation to be lossless), but there's no way to \
798                         guarantee that at the type level with Block's public API"
799                    ),
800                }
801            }
802        }
803    }
804
805    VIE::finish(&image)
806}
807
808pub fn write_image_terrain<
809    V: BaseVol<Vox = Block> + WriteVol,
810    P: PackingFormula,
811    VIE: VoxelImageEncoding + VoxelImageDecoding,
812>(
813    _: VIE,
814    packing: P,
815    vol: &mut V,
816    data: &VIE::Output,
817    lo: Vec3<u32>,
818    hi: Vec3<u32>,
819) -> Option<()> {
820    let ws = VIE::start(data)?;
821    let dims = Vec3::new(
822        hi.x.wrapping_sub(lo.x),
823        hi.y.wrapping_sub(lo.y),
824        hi.z.wrapping_sub(lo.z),
825    );
826    for z in 0..dims.z {
827        for y in 0..dims.y {
828            for x in 0..dims.x {
829                let (i, j) = packing.index(dims, x, y, z);
830                let is_border = x <= 1 || x >= dims.x - 2 || y <= 1 || y >= dims.y - 2;
831                let block = VIE::get_block(&ws, i, j, is_border);
832                if let Err(e) = vol.set(lo.as_() + Vec3::new(x, y, z).as_(), block) {
833                    warn!(
834                        "Error placing a block into a volume at {:?}: {:?}",
835                        (x, y, z),
836                        e
837                    );
838                }
839            }
840        }
841    }
842    Some(())
843}
844
845#[derive(Debug, Clone, Serialize, Deserialize)]
846pub struct WireChonk<VIE: VoxelImageEncoding, P: PackingFormula, M: Clone, S: RectVolSize> {
847    zmin: i32,
848    zmax: i32,
849    pub(crate) data: VIE::Output,
850    below: Block,
851    above: Block,
852    meta: M,
853    vie: VIE,
854    packing: P,
855    size: PhantomData<S>,
856}
857
858impl<VIE: VoxelImageEncoding + VoxelImageDecoding, P: PackingFormula, M: Clone, S: RectVolSize>
859    WireChonk<VIE, P, M, S>
860{
861    pub fn from_chonk(vie: VIE, packing: P, chonk: &Chonk<Block, S, M>) -> Option<Self> {
862        let data = image_terrain_chonk(&vie, packing, chonk)?;
863        Some(Self {
864            zmin: chonk.get_min_z(),
865            zmax: chonk.get_max_z(),
866            data,
867            below: *chonk
868                .get(Vec3::new(0, 0, chonk.get_min_z().saturating_sub(1)))
869                .ok()?,
870            above: *chonk.get(Vec3::new(0, 0, chonk.get_max_z() + 1)).ok()?,
871            meta: chonk.meta().clone(),
872            vie,
873            packing,
874            size: PhantomData,
875        })
876    }
877
878    pub fn to_chonk(&self) -> Option<Chonk<Block, S, M>> {
879        let mut chonk = Chonk::new(self.zmin, self.below, self.above, self.meta.clone());
880        write_image_terrain(
881            &self.vie,
882            self.packing,
883            &mut chonk,
884            &self.data,
885            Vec3::new(0, 0, self.zmin as u32),
886            Vec3::new(S::RECT_SIZE.x, S::RECT_SIZE.y, self.zmax as u32),
887        )?;
888        Some(chonk)
889    }
890}