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#[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
70pub 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#[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#[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
285const 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
302const 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
314const 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 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 0 => {
361 const SAMPLE_RADIUS: i32 = 2i32; 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 1 if N == 4 => {
385 const LANCZOS_A: f64 = 2.0; const SAMPLE_RADIUS: i32 = 2i32; const LANCZOS_LUT: [f64; lanczos_lookup_array_size(4, 2)] =
390 gen_lanczos_lookup::<4, 2>(LANCZOS_A);
391
392 let mut rgb: Vec3<f64> = Vec3::zero();
394 for dx in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
395 for dy in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
396 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 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 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 1 | 2 => {
424 const LANCZOS_A: f64 = 2.0; const SAMPLE_RADIUS: i32 = 2i32; let mut rgb: Vec3<f64> = Vec3::zero();
428 for dx in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
429 for dy in -SAMPLE_RADIUS..=SAMPLE_RADIUS {
430 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 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 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 _ => 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}