veloren_common_net/msg/
compression.rs

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