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 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));
}
}