minetest_worldmapper/
mapblock.rs

1use crate::config::Config;
2use crate::terrain::TerrainCell;
3use futures::stream::{Stream, StreamExt};
4use minetestworld::MAPBLOCK_LENGTH;
5use minetestworld::{MapBlock, Position};
6use std::collections::{BinaryHeap, HashMap};
7use std::ops::Range;
8
9pub const CHUNK_SIZE: usize = MAPBLOCK_LENGTH as usize * MAPBLOCK_LENGTH as usize;
10
11#[derive(Debug)]
12pub struct Bbox {
13    pub x: Range<i16>,
14    pub z: Range<i16>,
15}
16
17impl Bbox {
18    fn from_opt_ranges(x: Option<Range<i16>>, z: Option<Range<i16>>) -> Option<Bbox> {
19        Some(Bbox { x: x?, z: z? })
20    }
21}
22
23fn update_range(range: &mut Option<Range<i16>>, new_value: i16) {
24    if let Some(range) = range {
25        if new_value < range.start {
26            range.start = new_value;
27        } else if new_value >= range.end {
28            range.end = new_value + 1;
29        }
30    } else {
31        *range = Some(new_value..new_value + 1);
32    }
33}
34
35/// Allows efficient traversing the map from above
36///
37/// For a given (x,z) key, the value will be a max heap of all y values.
38type SortedPositions = HashMap<(i16, i16), BinaryHeap<i16>>;
39
40/// Analyzes the given position stream and returns its Bbox and SortedPosition
41///
42/// Takes a stream that yields Result<Position, _>.
43pub(crate) async fn analyze_positions<S, E>(mut positions: S) -> Result<(SortedPositions, Bbox), E>
44where
45    S: Stream<Item = Result<Position, E>> + Unpin,
46{
47    let mut sorted_positions = HashMap::new();
48    let (mut x_range, mut z_range) = (None, None);
49    while let Some(pos) = positions.next().await {
50        let pos = pos?;
51        update_range(&mut x_range, pos.x);
52        update_range(&mut z_range, pos.z);
53        let key = (pos.x, pos.z);
54        let y_stack = sorted_positions.entry(key).or_insert_with(BinaryHeap::new);
55        y_stack.push(pos.y);
56    }
57    Ok((
58        sorted_positions,
59        Bbox::from_opt_ranges(x_range, z_range).unwrap_or(Bbox { x: 0..0, z: 0..0 }),
60    ))
61}
62
63pub fn compute_mapblock(
64    mapblock: &MapBlock,
65    config: &Config,
66    base_height: i16,
67    acc: &mut [TerrainCell; CHUNK_SIZE],
68) {
69    if mapblock.name_id_mappings.values().eq([b"air"]) {
70        return;
71    }
72    for z in 0..MAPBLOCK_LENGTH {
73        for x in 0..MAPBLOCK_LENGTH {
74            let index = (x + MAPBLOCK_LENGTH * z) as usize;
75            if acc[index].alpha() > 230 {
76                continue;
77            }
78
79            for y in (0..MAPBLOCK_LENGTH).rev() {
80                let node = mapblock.get_node_at(Position::new(x, y, z));
81                if let Some(color) = config.get_color(&node.param0) {
82                    acc[index].add_background(*color);
83                    if config.hill_shading.enabled
84                        && acc[index].alpha() > config.hill_shading.min_alpha
85                    {
86                        acc[index].set_height(base_height + y as i16);
87                    }
88                    if acc[index].alpha() > config.sufficient_alpha {
89                        acc[index].set_height(base_height + y as i16);
90                        break;
91                    }
92                }
93            }
94        }
95    }
96}