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 / client / src / render / sky.rs

//! Sky geometry, port of `RenderGlobal.renderSky` and its display lists.

use std::f32::consts::PI;

use bytemuck::{Pod, Zeroable};
use common::math;
use glam::{Mat3, Vec3};

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable, Debug)]
pub struct SkyVertex {
    position: [f32; 3],
    uv: [f32; 2],
    color: u32,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SkyPass {
    /// Untextured and fogged: the sky and void planes.
    Plane,
    /// Alpha-blended sunrise fan.
    Sunrise,
    /// Additive celestial body with the given texture slot.
    Body(u32),
    /// Additive untextured stars.
    Stars,
}

pub struct SkyBatch {
    pub pass: SkyPass,
    pub first: u32,
    pub count: u32,
}

#[derive(Default)]
pub struct SkyGeometry {
    pub vertices: Vec<SkyVertex>,
    pub batches: Vec<SkyBatch>,
}

pub struct SkyParams {
    pub sky_color: Vec3,
    pub celestial_angle: f32,
    pub sunrise: Option<[f32; 4]>,
    pub sun_texture: u32,
    pub moon_texture: u32,
    /// Rain hides the sun and moon (`1 - getRainStrength`).
    pub clear: f32,
}

/// `RenderGlobal.renderStars`: 1500 random quads on a sphere of radius 100.
static STARS: std::sync::LazyLock<Vec<Vec3>> = std::sync::LazyLock::new(|| {
    let mut random = common::JavaRandom::new(10842);
    let mut corners = Vec::new();
    for _ in 0..1500 {
        let mut direction = [0; 3].map(|_| f64::from(random.next_float() * 2.0 - 1.0));
        let size = f64::from(0.25 + random.next_float() * 0.25);
        let length = direction.iter().map(|v| v * v).sum::<f64>();
        if !(length < 1.0 && length > 0.01) {
            continue;
        }
        direction = direction.map(|v| v / length.sqrt());
        let [x, y, z] = direction;
        let yaw = x.atan2(z);
        let (yaw_sin, yaw_cos) = (yaw.sin(), yaw.cos());
        let pitch = (x * x + z * z).sqrt().atan2(y);
        let (pitch_sin, pitch_cos) = (pitch.sin(), pitch.cos());
        let roll = random.next_double() * std::f64::consts::PI * 2.0;
        let (roll_sin, roll_cos) = (roll.sin(), roll.cos());
        for corner in 0..4 {
            let a = f64::from((corner & 2) - 1) * size;
            let b = f64::from(((corner + 1) & 2) - 1) * size;
            let rolled_a = a * roll_cos - b * roll_sin;
            let rolled_b = b * roll_cos + a * roll_sin;
            let up = rolled_a * pitch_sin;
            let out = -rolled_a * pitch_cos;
            let dx = out * yaw_sin - rolled_b * yaw_cos;
            let dz = rolled_b * yaw_sin + out * yaw_cos;
            corners.push(Vec3::new(
                (x * 100.0 + dx) as f32,
                (y * 100.0 + up) as f32,
                (z * 100.0 + dz) as f32,
            ));
        }
    }
    corners
});

/// `World.getStarBrightness`.
pub fn star_brightness(angle: f32) -> f32 {
    let light = (1.0 - (math::cos(angle * PI * 2.0) * 2.0 + 0.75)).clamp(0.0, 1.0);
    light * light * 0.5
}

fn pack(color: [f32; 4]) -> u32 {
    let channel = |c: f32| (c.clamp(0.0, 1.0) * 255.0) as u32;
    channel(color[0]) | channel(color[1]) << 8 | channel(color[2]) << 16 | channel(color[3]) << 24
}

impl SkyGeometry {
    fn batch(&mut self, pass: SkyPass, vertices: impl IntoIterator<Item = SkyVertex>) {
        let first = self.vertices.len() as u32;
        self.vertices.extend(vertices);
        self.batches.push(SkyBatch {
            pass,
            first,
            count: self.vertices.len() as u32 - first,
        });
    }

    /// Splits quads, given as corner lists, into triangles.
    fn quads(corners: &[SkyVertex]) -> impl Iterator<Item = SkyVertex> + '_ {
        corners
            .as_chunks::<4>()
            .0
            .iter()
            .flat_map(|q| [q[0], q[1], q[2], q[0], q[2], q[3]])
    }

    pub fn clear(&mut self) {
        self.vertices.clear();
        self.batches.clear();
    }

    pub fn build(&mut self, params: &SkyParams) {
        self.clear();
        let vertex = |position: Vec3, uv: [f32; 2], color: u32| SkyVertex {
            position: position.to_array(),
            uv,
            color,
        };

        let sky = pack(params.sky_color.extend(1.0).to_array());
        let plane = |height: f32, color: u32| {
            const STEP: i32 = 64;
            const EXTENT: i32 = STEP * (256 / STEP + 2);
            let mut corners = Vec::new();
            for x in (-EXTENT..=EXTENT).step_by(STEP as usize) {
                for z in (-EXTENT..=EXTENT).step_by(STEP as usize) {
                    let (x0, z0, x1, z1) =
                        (x as f32, z as f32, (x + STEP) as f32, (z + STEP) as f32);
                    for (px, pz) in [(x0, z0), (x1, z0), (x1, z1), (x0, z1)] {
                        corners.push(vertex(Vec3::new(px, height, pz), [0.0; 2], color));
                    }
                }
            }
            corners
        };
        let sky_plane = plane(16.0, sky);
        self.batch(SkyPass::Plane, Self::quads(&sky_plane));

        if let Some([r, g, b, a]) = params.sunrise {
            let flip = if params.celestial_angle > 0.5 {
                PI
            } else {
                0.0
            };
            let rotation = Mat3::from_rotation_x(90f32.to_radians()) * Mat3::from_rotation_z(flip);
            let center = vertex(rotation * (Vec3::Y * 100.0), [0.0; 2], pack([r, g, b, a]));
            let rim: Vec<SkyVertex> = (0..=16)
                .map(|i| {
                    let angle = i as f32 * PI * 2.0 / 16.0;
                    let (s, c) = (math::sin(angle), math::cos(angle));
                    let position = rotation * Vec3::new(s * 120.0, c * 120.0, -c * 40.0 * a);
                    vertex(position, [0.0; 2], pack([r, g, b, 0.0]))
                })
                .collect();
            self.batch(
                SkyPass::Sunrise,
                rim.windows(2).flat_map(|edge| [center, edge[0], edge[1]]),
            );
        }

        let rotation = Mat3::from_rotation_x((params.celestial_angle * 360.0).to_radians());
        let white = pack([1.0, 1.0, 1.0, params.clear]);
        let body = |size: f32, height: f32, uvs: [[f32; 2]; 4], flip: bool| -> Vec<SkyVertex> {
            let corners = if flip {
                [(-size, size), (size, size), (size, -size), (-size, -size)]
            } else {
                [(-size, -size), (size, -size), (size, size), (-size, size)]
            };
            corners
                .iter()
                .zip(uvs)
                .map(|(&(x, z), uv)| vertex(rotation * Vec3::new(x, height, z), uv, white))
                .collect()
        };
        let sun = body(
            30.0,
            100.0,
            [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
            false,
        );
        self.batch(SkyPass::Body(params.sun_texture), Self::quads(&sun));
        let moon = body(
            20.0,
            -100.0,
            [[1.0, 1.0], [0.0, 1.0], [0.0, 0.0], [1.0, 0.0]],
            true,
        );
        self.batch(SkyPass::Body(params.moon_texture), Self::quads(&moon));
        let stars = star_brightness(params.celestial_angle) * params.clear;
        if stars > 0.0 {
            let color = pack([stars; 4]);
            let corners: Vec<SkyVertex> = STARS
                .iter()
                .map(|&corner| vertex(rotation * corner, [0.0; 2], color))
                .collect();
            self.batch(SkyPass::Stars, Self::quads(&corners));
        }

        let void = params.sky_color * Vec3::new(0.2, 0.2, 0.6) + Vec3::new(0.04, 0.04, 0.1);
        let void_plane = plane(-16.0, pack(void.extend(1.0).to_array()));
        self.batch(SkyPass::Plane, Self::quads(&void_plane));
    }
}