Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / server / src / player / maps.rs

use common::item::Item;
use common::map::material_color;
use common::math::floor_f64;
use common::protocol::Packet;
use common::world::{Block, ChunkPos};
use glam::DVec3;

use super::ServerPlayer;
use crate::level::Level;
use crate::save::maps::{Holder, StoredMap};

pub(super) struct Tracking {
    revisions: [u64; 128],
    next: usize,
    calls: u32,
    icons: Vec<u8>,
}
impl Default for Tracking {
    fn default() -> Self {
        Self {
            revisions: [u64::MAX; 128],
            next: 0,
            calls: 0,
            icons: Vec::new(),
        }
    }
}

impl ServerPlayer {
    pub(super) fn create_map(&self, level: &mut Level) -> Option<i32> {
        let [x, _, z] = self.position(level).to_array().map(floor_f64);
        let result = level.maps.lock().unwrap().create([x, z], level.dimension());
        let id = result.map_err(|error| level.fail_storage(error)).ok()?;
        Some(i32::from(id))
    }

    pub(super) fn tick_maps(&mut self, level: &mut Level) {
        if !self
            .inventory
            .main
            .iter()
            .flatten()
            .any(|stack| stack.item == Item::MAP)
        {
            if !self.map_tracking.is_empty() {
                level.maps.lock().unwrap().holders.remove(&self.entity_id);
                self.map_tracking.clear();
            }
            return;
        }
        let store = level.maps.clone();
        let mut maps = store.lock().unwrap();
        let dimension = level.dimension();
        let position = self.position(level);
        let yaw = self.entity(level).map_or(0.0, |e| e.yaw);
        let mut ids = Vec::new();
        let selected = self.inventory.selected;
        for (slot, item) in self.inventory.main.iter_mut().enumerate() {
            let Some(stack) = item.as_mut().filter(|s| s.item == Item::MAP) else {
                continue;
            };
            let mut id = stack.damage as i16;
            let created = match maps.load(id) {
                Ok(false) => maps
                    .create([self.spawn[0], self.spawn[2]], dimension)
                    .map(Some),
                loaded => loaded.map(|_| None),
            };
            match created {
                Ok(Some(created)) => {
                    id = created;
                    stack.damage = i32::from(id);
                }
                Ok(None) => {}
                Err(error) => return level.fail_storage(error),
            }
            if !ids.contains(&id) {
                ids.push(id);
            }
            if slot == selected
                && let Some(map) = maps.loaded.get_mut(&id)
            {
                update_map(level, position, map);
            }
        }
        maps.holders.retain(|&id, holder| {
            holder.dimension != dimension || level.entities.get(id).is_some_and(|e| !e.entity.dead)
        });
        maps.holders.insert(
            self.entity_id,
            Holder {
                maps: ids.clone(),
                position,
                yaw,
                dimension,
            },
        );
        self.map_tracking.retain(|id, _| ids.contains(id));
        for id in ids {
            let map = &maps.loaded[&id];
            let mut icons = vec![1];
            let mut holders: Vec<_> = maps.holders.iter().collect();
            holders.sort_unstable_by_key(|(id, _)| **id);
            for (_, holder) in holders {
                if holder.dimension != map.map.dimension || !holder.maps.contains(&id) {
                    continue;
                }
                let scale = (1 << map.map.scale) as f32;
                let x = (holder.position.x - f64::from(map.map.center[0])) as f32 / scale;
                let z = (holder.position.z - f64::from(map.map.center[1])) as f32 / scale;
                if !(-64.0..=64.0).contains(&x) || !(-64.0..=64.0).contains(&z) {
                    continue;
                }
                let rotation = if map.map.dimension < 0 {
                    let t = map.ticks / 10;
                    ((t.wrapping_mul(t)
                        .wrapping_mul(34187121)
                        .wrapping_add(t.wrapping_mul(121))
                        >> 15)
                        & 15) as u8
                } else {
                    ((f64::from(holder.yaw * 16.0 / 360.0) + 0.5) as i32 & 15) as u8
                };
                icons.extend([
                    rotation << 4,
                    (f64::from(x * 2.0) + 0.5) as i32 as u8,
                    (f64::from(z * 2.0) + 0.5) as i32 as u8,
                ]);
            }
            let track = self.map_tracking.entry(id).or_default();
            track.calls = track.calls.wrapping_add(1);
            let data = if track.calls % 5 == 1 && track.icons != icons {
                track.icons = icons.clone();
                Some(icons)
            } else {
                (0..128).find_map(|_| {
                    let x = track.next * 11 % 128;
                    track.next = (track.next + 1) % 128;
                    if track.revisions[x] == map.revisions[x] {
                        return None;
                    }
                    track.revisions[x] = map.revisions[x];
                    let column = (0..128).map(|y| map.map.colors[x + y * 128]);
                    Some([0, x as u8, 0].into_iter().chain(column).collect())
                })
            };
            if let Some(data) = data {
                self.send(Packet::MapData {
                    item: Item::MAP.0,
                    map: id,
                    data,
                });
            }
        }
    }
}

fn update_map(level: &mut Level, position: DVec3, stored: &mut StoredMap) {
    if level.dimension() != stored.map.dimension {
        return;
    }
    let center = stored.map.center;
    let scale = 1_i32 << stored.map.scale;
    let px = floor_f64(position.x - f64::from(center[0])) / scale + 64;
    let pz = floor_f64(position.z - f64::from(center[1])) / scale + 64;
    let radius = 128 / scale / if level.dimension() < 0 { 2 } else { 1 };
    stored.ticks = stored.ticks.wrapping_add(1);
    for x in (px - radius + 1).max(0)..(px + radius).min(128) {
        if x & 15 != stored.ticks & 15 {
            continue;
        }
        let mut previous_height = 0.0;
        let mut changed = false;
        for z in (pz - radius - 1).max(-1)..(pz + radius).min(128) {
            let dx = x - px;
            let dz = z - pz;
            let wx = (center[0] / scale + x - 64) * scale;
            let wz = (center[1] / scale + z - 64) * scale;
            let mut counts = [0; 256];
            let mut height = 0.0;
            let mut depth = 0;
            if level.dimension() < 0 {
                let hash = wx.wrapping_add(wz.wrapping_mul(231871));
                let hash = hash
                    .wrapping_mul(hash)
                    .wrapping_mul(31287121)
                    .wrapping_add(hash.wrapping_mul(11));
                let block = if (hash >> 20) & 1 == 0 {
                    Block::DIRT
                } else {
                    Block::STONE
                };
                counts[usize::from(block.0)] = 10;
                height = 100.0;
            } else {
                let pos = ChunkPos::containing(wx, wz);
                level.load_chunk(pos);
                let chunk = level.chunk(pos).expect("loaded map chunk");
                for sx in 0..scale {
                    for sz in 0..scale {
                        let lx = ((wx + sx) & 15) as usize;
                        let lz = ((wz + sz) & 15) as usize;
                        let mut y = (chunk.height(lx, lz) + 1).min(128);
                        let mut block = Block::AIR;
                        if y > 1 {
                            loop {
                                block = chunk.block(lx, y - 1, lz);
                                if !block.is_air() && material_color(block.material()) != 0
                                    || y == 1
                                {
                                    break;
                                }
                                y -= 1;
                            }
                            if block.material().is_liquid() {
                                let mut below = y as i32 - 1;
                                loop {
                                    let liquid =
                                        chunk.block(lx, below as usize, lz).material().is_liquid();
                                    depth += 1;
                                    below -= 1;
                                    if below <= 0 || !liquid {
                                        break;
                                    }
                                }
                            }
                        }
                        height += y as f64 / f64::from(scale * scale);
                        counts[usize::from(block.0)] += 1;
                    }
                }
            }
            depth /= scale * scale;
            let block = Block(
                counts
                    .iter()
                    .enumerate()
                    .max_by_key(|(index, count)| (**count, std::cmp::Reverse(*index)))
                    .unwrap()
                    .0 as u8,
            );
            let color = material_color(block.material());
            let mut slope = (height - previous_height) * 4.0 / f64::from(scale + 4)
                + (f64::from((x + z) & 1) - 0.5) * 0.4;
            let mut shade = if slope > 0.6 {
                2
            } else if slope < -0.6 {
                0
            } else {
                1
            };
            if color == 12 {
                slope = f64::from(depth) * 0.1 + f64::from((x + z) & 1) * 0.2;
                shade = if slope < 0.5 {
                    2
                } else if slope > 0.9 {
                    0
                } else {
                    1
                };
            }
            previous_height = height;
            let edge = dx * dx + dz * dz > (radius - 2) * (radius - 2);
            if z >= 0 && dx * dx + dz * dz < radius * radius && (!edge || (x + z) & 1 != 0) {
                let pixel = &mut stored.map.colors[(x + z * 128) as usize];
                let next = color * 4 + shade;
                if *pixel != next {
                    *pixel = next;
                    changed = true;
                }
            }
        }
        if changed {
            stored.changed(x as usize);
        }
    }
}