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

//! Client weather: lightning bolts (`EntityLightningBolt` on the client) and the weather tint
//! of the sky, fog and clouds.

use common::JavaRandom;
use common::world::weather::Weather;
use glam::{DVec3, Vec3};

/// A lightning bolt, which only flashes and thunders on the client.
pub struct Bolt {
    pub position: DVec3,
    /// `boltVertex`: seeds the bolt's shape.
    pub vertex: i64,
    state: i32,
    flashes: i32,
    rand: JavaRandom,
}

impl Bolt {
    pub fn new(position: DVec3, mut rand: JavaRandom) -> Self {
        Self {
            position,
            vertex: rand.next_long(),
            state: 2,
            flashes: rand.next_int(3) + 1,
            rand,
        }
    }

    /// `onUpdate`: thunder when it strikes, then a few flashes. Returns false when gone.
    pub fn tick(&mut self, sounds: &mut Vec<(String, DVec3, f32, f32)>, flash: &mut i32) -> bool {
        if self.state == 2 {
            let pitch = 0.8 + self.rand.next_float() * 0.2;
            let thunder = (
                "ambient.weather.thunder".into(),
                self.position,
                10_000.0,
                pitch,
            );
            sounds.push(thunder);
            let pitch = 0.5 + self.rand.next_float() * 0.2;
            sounds.push(("random.explode".into(), self.position, 2.0, pitch));
        }
        self.state -= 1;
        if self.state < 0 {
            if self.flashes == 0 {
                return false;
            }
            if self.state < -self.rand.next_int(10) {
                self.flashes -= 1;
                self.state = 1;
                self.vertex = self.rand.next_long();
            }
        }
        if self.state >= 0 {
            *flash = 2;
        }
        true
    }

    /// Whether the bolt is drawn this tick (`lightningState >= 0` keeps it visible).
    pub fn visible(&self) -> bool {
        self.state >= 0
    }
}

/// Mixes towards gray as `World.getSkyColor` and `drawClouds` do for rain and thunder.
fn overcast(color: Vec3, strength: f32, gray: f32, amount: f32) -> Vec3 {
    if strength <= 0.0 {
        return color;
    }
    let luminance = (color.x * 0.3 + color.y * 0.59 + color.z * 0.11) * gray;
    let keep = 1.0 - strength * amount;
    color * keep + Vec3::splat(luminance * (1.0 - keep))
}

/// The weather part of `World.getSkyColor`, with the white-blue of a lightning flash.
pub fn sky_color(color: Vec3, weather: &Weather, flash: i32, partial: f32) -> Vec3 {
    let mut color = overcast(color, weather.rain_strength(partial), 0.6, 0.75);
    color = overcast(color, weather.thunder_strength(partial), 0.2, 0.75);
    if flash > 0 {
        let flash = (flash as f32 - partial).min(1.0) * 0.45;
        color = color * (1.0 - flash) + Vec3::new(0.8, 0.8, 1.0) * flash;
    }
    color
}

/// The weather part of `EntityRenderer.updateFogColor`.
pub fn fog_color(mut color: Vec3, weather: &Weather, partial: f32) -> Vec3 {
    let rain = weather.rain_strength(partial);
    if rain > 0.0 {
        color *= Vec3::new(1.0 - rain * 0.5, 1.0 - rain * 0.5, 1.0 - rain * 0.4);
    }
    let thunder = weather.thunder_strength(partial);
    if thunder > 0.0 {
        color *= 1.0 - thunder * 0.5;
    }
    color
}

/// `World.drawClouds`: white clouds darkened by night, rain and thunder.
pub fn cloud_color(angle: f32, weather: &Weather, partial: f32) -> Vec3 {
    let light = crate::environment::daylight(angle);
    let color = overcast(Vec3::ONE, weather.rain_strength(partial), 0.6, 0.95);
    let color = color * Vec3::new(light * 0.9 + 0.1, light * 0.9 + 0.1, light * 0.85 + 0.15);
    overcast(color, weather.thunder_strength(partial), 0.2, 0.95)
}

/// `EntityRenderer.addRainParticles`: splashes where rain lands near the viewer, smoke on
/// lava, and now and then the sound of rain.
pub fn rain_particles(
    world: &mut crate::world::ClientWorld,
    viewer: DVec3,
    ticks: i32,
    fancy: bool,
    sound_counter: &mut i32,
) {
    use common::math::floor_f64;
    use common::world::shape::{self, BlockView};
    let strength = world.weather.rain_strength(1.0) / if fancy { 1.0 } else { 2.0 };
    if strength == 0.0 {
        return;
    }
    let mut random = JavaRandom::new(i64::from(ticks) * 312_987_231);
    let [px, py, pz] = viewer.to_array().map(floor_f64);
    const RANGE: i32 = 10;
    let mut sound = DVec3::ZERO;
    let mut splashes = 0;
    for _ in 0..(100.0 * strength * strength) as i32 {
        let x = px + random.next_int(RANGE) - random.next_int(RANGE);
        let z = pz + random.next_int(RANGE) - random.next_int(RANGE);
        let top = world.top_solid_or_liquid(x, z);
        let below = BlockView::block(&*world, x, top - 1, z);
        if top > py + RANGE || top < py - RANGE || !world.biome(x, z).rains() {
            continue;
        }
        let (dx, dz) = (random.next_float(), random.next_float());
        if below.is_air() {
            continue;
        }
        let floor = shape::block_bounds(&*world, below, x, top - 1, z).min.y;
        let point = DVec3::new(
            f64::from(x as f32 + dx),
            f64::from(top as f32 + 0.1) - floor,
            f64::from(z as f32 + dz),
        );
        if below.material() == common::world::Material::Lava {
            world.particles.rain_smoke(point);
        } else {
            splashes += 1;
            if random.next_int(splashes) == 0 {
                sound = point;
            }
            world.particles.raindrop(point);
        }
    }
    if splashes > 0 && random.next_int(3) < *sound_counter {
        *sound_counter = 0;
        let sheltered = sound.y > viewer.y + 1.0 && world.top_solid_or_liquid(px, pz) > py;
        let (volume, pitch) = if sheltered { (0.1, 0.5) } else { (0.2, 1.0) };
        let rain = ("ambient.weather.rain".into(), sound, volume, pitch);
        world.sounds.push(rain);
    } else if splashes > 0 {
        *sound_counter += 1;
    }
}