alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH 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
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;
}
}