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 / scene / weather.rs

//! Precipitation (`EntityRenderer.renderRainSnow`) and lightning (`RenderLightningBolt`).

use common::JavaRandom;
use common::math::floor_f64;
use glam::{DVec3, Vec3};

use super::{Geometry, brightness_at};
use crate::world::ClientWorld;

/// The per-column seed of `renderRainSnow`, computed in `int` like the original.
fn column_seed(x: i32, z: i32) -> i32 {
    x.wrapping_mul(x)
        .wrapping_mul(3121)
        .wrapping_add(x.wrapping_mul(45_238_971))
        .wrapping_add(z.wrapping_mul(z).wrapping_mul(418_711))
        .wrapping_add(z.wrapping_mul(13_761))
}

pub struct Viewer {
    /// The entity position, which picks the columns.
    pub position: DVec3,
    /// The interpolated eye, which vertices are relative to.
    pub eye: DVec3,
}

impl Geometry {
    /// Falling rain and snow in the columns around the viewer.
    pub fn precipitation(
        &mut self,
        world: &ClientWorld,
        viewer: &Viewer,
        ticks: i32,
        partial: f32,
        fancy: bool,
        sky_darkness: u32,
    ) {
        let start = self.vertices.len() as u32;
        self.weather = start..start;
        let strength = world.weather.rain_strength(partial);
        if strength <= 0.0 {
            return;
        }
        let [px, py, pz] = viewer.position.to_array().map(floor_f64);
        let radius = if fancy { 10 } else { 5 };
        let size = (radius * 2 + 1) as usize;
        let biomes = world
            .climate()
            .sample(px - radius, pz - radius, size, size)
            .biomes;
        let ticks_f = ticks as f32 + partial;
        let slide = ((ticks & 511) as f32 + partial) / 512.0;
        let base_y = floor_f64(viewer.eye.y);
        let fade = |x: i32, z: i32| {
            let dx = f64::from(x as f32 + 0.5) - viewer.position.x;
            let dz = f64::from(z as f32 + 0.5) - viewer.position.z;
            (dx * dx + dz * dz).sqrt() as f32 / radius as f32
        };
        let snow = crate::assets::entity_texture("environment/snow.png");
        let rain = crate::assets::entity_texture("environment/rain.png");
        for pass in [true, false] {
            let mut index = 0;
            for x in px - radius..=px + radius {
                for z in pz - radius..=pz + radius {
                    let biome = biomes[index];
                    index += 1;
                    if (pass && !biome.snows()) || (!pass && !biome.rains()) {
                        continue;
                    }
                    let top = world.top_solid_or_liquid(x, z);
                    let (low, high) = ((py - radius).max(top), (py + radius).max(top));
                    if low == high {
                        continue;
                    }
                    let mut random = JavaRandom::new(i64::from(column_seed(x, z)));
                    let distance = fade(x, z);
                    let (cx, cz) = (f64::from(x), f64::from(z));
                    let (v, u, light, alpha, texture) = if pass {
                        let u =
                            random.next_float() + ticks_f * 0.01 * random.next_gaussian() as f32;
                        let jitter =
                            random.next_float() + ticks_f * random.next_gaussian() as f32 * 0.001;
                        let lit = f64::from(top.max(0).max(base_y));
                        let light = brightness_at(world, DVec3::new(cx, lit, cz), sky_darkness);
                        let alpha = ((1.0 - distance * distance) * 0.3 + 0.5) * strength;
                        let v = |y: i32| y as f32 / 4.0 + slide + jitter;
                        ([v(low), v(high)], u, light, alpha, snow)
                    } else {
                        let scroll =
                            ((ticks.wrapping_add(column_seed(x, z)) & 31) as f32 + partial) / 32.0
                                * (3.0 + random.next_float());
                        let light = brightness_at(world, DVec3::new(cx, 127.0, cz), sky_darkness)
                            * 0.85
                            + 0.15;
                        let alpha = ((1.0 - distance * distance) * 0.5 + 0.5) * strength;
                        let v = |y: i32| y as f32 / 4.0 + scroll;
                        ([v(low), v(high)], 0.0, light, alpha, rain)
                    };
                    let uv = [u, v[0], 1.0, v[1] - v[0]];
                    let color = ((alpha.clamp(0.0, 1.0) * 255.0) as u32 * 0x0100_0000)
                        | ((light * 255.0) as u32 * 0x010101);
                    let at = |(dx, dz): (f64, f64), y: i32| {
                        (DVec3::new(cx + dx, f64::from(y), cz + dz) - viewer.eye).as_vec3()
                    };
                    for (a, b) in [((0.0, 0.5), (1.0, 0.5)), ((0.5, 0.0), (0.5, 1.0))] {
                        let points = [at(a, low), at(b, low), at(b, high), at(a, high)];
                        self.quad(points, uv, texture, color);
                    }
                }
            }
        }
        self.weather.end = self.vertices.len() as u32;
    }

    /// `RenderLightningBolt`: four widening layers of jagged glowing strips.
    pub fn bolts(&mut self, world: &ClientWorld, eye: DVec3) {
        let start = self.vertices.len() as u32;
        self.bolts = start..start;
        for bolt in world.bolts.iter().filter(|bolt| bolt.visible()) {
            let origin = (bolt.position - eye).as_vec3();
            let mut offsets = [[0.0_f32; 2]; 8];
            let (mut x, mut z) = (0.0_f32, 0.0_f32);
            let mut random = JavaRandom::new(bolt.vertex);
            for step in (0..8).rev() {
                offsets[step] = [x, z];
                x += (random.next_int(11) - 5) as f32;
                z += (random.next_int(11) - 5) as f32;
            }
            for layer in 0..4 {
                let mut random = JavaRandom::new(bolt.vertex);
                for branch in 0..3 {
                    let top: i32 = if branch > 0 { 7 - branch } else { 7 };
                    let bottom = if branch > 0 { top - 2 } else { 0 };
                    let mut dx = offsets[top as usize][0] - x;
                    let mut dz = offsets[top as usize][1] - z;
                    for segment in (bottom..=top).rev() {
                        let (upper_x, upper_z) = (dx, dz);
                        let spread = if branch == 0 { 11 } else { 31 };
                        dx += (random.next_int(spread) - spread / 2) as f32;
                        dz += (random.next_int(spread) - spread / 2) as f32;
                        let width = |extra: i32| {
                            let mut width = 0.1 + layer as f32 * 0.2;
                            if branch == 0 {
                                width *= (segment + extra) as f32 * 0.1 + 1.0;
                            }
                            width
                        };
                        let (upper, lower) = (width(0), width(-1));
                        let corner = |index: usize, width: f32| {
                            let shift =
                                |far: bool| 0.5 - width + if far { width * 2.0 } else { 0.0 };
                            (shift(matches!(index, 1 | 2)), shift(matches!(index, 2 | 3)))
                        };
                        for side in 0..4 {
                            let point = |index: usize, width: f32, offset: (f32, f32), y: i32| {
                                let (x, z) = corner(index % 4, width);
                                origin + Vec3::new(x + offset.0, (y * 16) as f32, z + offset.1)
                            };
                            let (a, b) = (side, side + 1);
                            self.quad(
                                [
                                    point(a, lower, (dx, dz), segment),
                                    point(a, upper, (upper_x, upper_z), segment + 1),
                                    point(b, upper, (upper_x, upper_z), segment + 1),
                                    point(b, lower, (dx, dz), segment),
                                ],
                                [0.0; 4],
                                -1,
                                0x4c7f_7272,
                            );
                        }
                    }
                }
            }
        }
        self.bolts.end = self.vertices.len() as u32;
    }
}

impl Geometry {
    /// `RenderGlobal.renderClouds` and `renderCloudsFancy`: the drifting cloud layer at height
    /// 108, flat or as boxes four blocks thick.
    pub fn clouds(&mut self, camera: DVec3, drift: i32, partial: f32, color: Vec3, fancy: bool) {
        let start = self.vertices.len() as u32;
        let texture = crate::assets::entity_texture("environment/clouds.png");
        let shade = |factor: f32| {
            let [r, g, b] = (color * factor)
                .to_array()
                .map(|c| (c.clamp(0.0, 1.0) * 255.0) as u32);
            204 << 24 | r | g << 8 | b << 16
        };
        let height = 108.0 - camera.y as f32 + 0.33;
        let drift = f64::from((drift as f32 + partial) * 0.03);
        if !fancy {
            const STEP: i32 = 32;
            const UV: f32 = 0.5 / 1024.0;
            let mut x = camera.x + drift;
            let mut z = camera.z;
            x -= f64::from(floor_f64(x / 2048.0) * 2048);
            z -= f64::from(floor_f64(z / 2048.0) * 2048);
            let (u, v) = ((x * f64::from(UV)) as f32, (z * f64::from(UV)) as f32);
            let color = shade(1.0);
            for gx in (-STEP * 8..STEP * 8).step_by(STEP as usize) {
                for gz in (-STEP * 8..STEP * 8).step_by(STEP as usize) {
                    let (x0, z0) = (gx as f32, gz as f32);
                    let (x1, z1) = (x0 + STEP as f32, z0 + STEP as f32);
                    self.quad(
                        [
                            Vec3::new(x0, height, z0),
                            Vec3::new(x1, height, z0),
                            Vec3::new(x1, height, z1),
                            Vec3::new(x0, height, z1),
                        ],
                        [x0 * UV + u, z0 * UV + v, STEP as f32 * UV, STEP as f32 * UV],
                        texture,
                        color,
                    );
                }
            }
            self.clouds = start..self.vertices.len() as u32;
            self.fancy_clouds = false;
            return;
        }
        const SCALE: f32 = 12.0;
        const THICKNESS: f32 = 4.0;
        const CELLS: i32 = 8;
        const UV: f32 = 0.003_906_25;
        const EDGE: f32 = 1.0 / 1024.0;
        let mut x = (camera.x + drift) / f64::from(SCALE);
        let mut z = camera.z / f64::from(SCALE) + f64::from(0.33_f32);
        x -= f64::from(floor_f64(x / 2048.0) * 2048);
        z -= f64::from(floor_f64(z / 2048.0) * 2048);
        let (u0, v0) = (floor_f64(x) as f32 * UV, floor_f64(z) as f32 * UV);
        let (fx, fz) = (
            (x - f64::from(floor_f64(x))) as f32,
            (z - f64::from(floor_f64(z))) as f32,
        );
        let point = |x: f32, y: f32, z: f32| Vec3::new(x * SCALE, y, z * SCALE);
        let span = CELLS as f32;
        for gx in -2..=3 {
            for gz in -2..=3 {
                let (cx, cz) = ((gx * CELLS) as f32, (gz * CELLS) as f32);
                let (x0, z0) = (cx - fx, cz - fz);
                let uv = |du: f32, dv: f32| [(cx + du) * UV + u0, (cz + dv) * UV + v0];
                let [u, v] = uv(0.0, 0.0);
                let flat = |geometry: &mut Self, y: f32, color: u32| {
                    let points = [
                        point(x0, y, z0),
                        point(x0 + span, y, z0),
                        point(x0 + span, y, z0 + span),
                        point(x0, y, z0 + span),
                    ];
                    geometry.quad(points, [u, v, span * UV, span * UV], texture, color);
                };
                if height > -THICKNESS - 1.0 {
                    flat(self, height, shade(0.7));
                }
                if height <= THICKNESS + 1.0 {
                    flat(self, height + THICKNESS - EDGE, shade(1.0));
                }
                let sides = shade(0.9);
                for cell in 0..CELLS {
                    let column = cell as f32;
                    let [side_u, _] = uv(column + 0.5, 0.0);
                    for (show, offset) in [(gx > -1, 0.0), (gx <= 1, 1.0 - EDGE)] {
                        if show {
                            let px = x0 + column + offset;
                            let points = [
                                point(px, height, z0),
                                point(px, height + THICKNESS, z0),
                                point(px, height + THICKNESS, z0 + span),
                                point(px, height, z0 + span),
                            ];
                            self.quad(points, [side_u, v, 0.0, span * UV], texture, sides);
                        }
                    }
                }
                let ends = shade(0.8);
                for cell in 0..CELLS {
                    let row = cell as f32;
                    let [_, end_v] = uv(0.0, row + 0.5);
                    for (show, offset) in [(gz > -1, 0.0), (gz <= 1, 1.0 - EDGE)] {
                        if show {
                            let pz = z0 + row + offset;
                            let points = [
                                point(x0, height, pz),
                                point(x0 + span, height, pz),
                                point(x0 + span, height + THICKNESS, pz),
                                point(x0, height + THICKNESS, pz),
                            ];
                            self.quad(points, [u, end_v, span * UV, 0.0], texture, ends);
                        }
                    }
                }
            }
        }
        self.clouds = start..self.vertices.len() as u32;
        self.fancy_clouds = true;
    }
}