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 scene mod.rs
use std::ops::Range;
mod effects;
mod fishing;
mod hand;
mod map;
mod models;
mod moving;
mod paintings;
mod player;
mod signs;
mod vehicles;
mod weather;
pub use moving::Style;
pub(super) use player::portrait_projection;
pub use weather::Viewer;
use bytemuck::{Pod, Zeroable};
use common::entity::mob::MobKind;
use common::world::{shape, texture};
use glam::{DVec3, Mat3, Vec3};
use crate::entities::{Entities, Kind};
use crate::interaction::Interaction;
use crate::player::LocalPlayer;
use crate::world::ClientWorld;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Vertex {
position: [f32; 3],
uv: [f32; 2],
color: u32,
pub(super) texture: i32,
}
#[derive(Default)]
pub struct Geometry {
pub vertices: Vec<Vertex>,
pub map: Option<i16>,
pub map_text: Range<u32>,
map_transform: Option<glam::Mat4>,
pub objects: Range<u32>,
pub lines: Range<u32>,
pub outline: Range<u32>,
pub cracks: Range<u32>,
pub hand: Range<u32>,
pub portrait: Range<u32>,
/// Rain and snow, blended over the translucent terrain.
pub weather: Range<u32>,
/// Lightning, drawn additively.
pub bolts: Range<u32>,
pub clouds: Range<u32>,
/// Blocks pistons are moving.
pub moving: Range<u32>,
/// `GameSettings.hideGUI` also hides the hand.
pub hide_hand: bool,
/// Sign boards and their text.
pub signs: Range<u32>,
pub names: Range<u32>,
pub names_hidden: Range<u32>,
/// Fancy graphics draw entity shadows.
pub fancy: bool,
/// Players with a fishing hook out, whose rods show the cast icon.
anglers: Vec<i32>,
/// The shells of charged creepers, drawn additively.
auras: Vec<Vertex>,
/// Entity shadows, blended without writing depth.
pub shadows: Range<u32>,
/// The white flashes of primed TNT, drawn additively.
pub flashes: Range<u32>,
/// Fancy clouds first fill the depth buffer so only their outer faces show.
pub fancy_clouds: bool,
}
/// The unit offsets towards each face, in `Facing` order.
const SIDES: [DVec3; 6] = [
DVec3::NEG_Y,
DVec3::Y,
DVec3::NEG_Z,
DVec3::Z,
DVec3::NEG_X,
DVec3::X,
];
/// A billboarded item icon, half a block wide.
const SPRITE: [Vec3; 4] = [
Vec3::new(-0.25, 0.375, 0.0),
Vec3::new(0.25, 0.375, 0.0),
Vec3::new(0.25, -0.125, 0.0),
Vec3::new(-0.25, -0.125, 0.0),
];
/// An opaque gray of the given brightness.
fn gray(brightness: f32) -> u32 {
0xff000000 | ((brightness * 255.0) as u32 * 0x010101)
}
/// The rectangle of a tile in a 16 by 16 atlas.
fn tile_uv(tile: u8) -> [f32; 4] {
let size = 15.99 / 256.0;
[
f32::from(tile & 15) / 16.0,
f32::from(tile >> 4) / 16.0,
size,
size,
]
}
fn lerp(previous: f32, current: f32, partial: f32) -> f32 {
previous + (current - previous) * partial
}
/// The interpolated progress of an arm swing.
fn swing(living: &common::entity::Living, partial: f32) -> f32 {
living.prev_swing_progress
+ (living.swing_progress - living.prev_swing_progress).rem_euclid(1.0) * partial
}
fn brightness_at(world: &ClientWorld, p: DVec3, sky_darkness: u32) -> f32 {
world.brightness[usize::from(world.light_level(p.x, p.y, p.z, sky_darkness))]
}
/// The corners of a box: the bottom four counterclockwise from `min`, then the top four.
fn box_corners(min: Vec3, max: Vec3) -> [Vec3; 8] {
[
min,
Vec3::new(max.x, min.y, min.z),
Vec3::new(max.x, max.y, min.z),
Vec3::new(min.x, max.y, min.z),
Vec3::new(min.x, min.y, max.z),
Vec3::new(max.x, min.y, max.z),
max,
Vec3::new(min.x, max.y, max.z),
]
}
impl Geometry {
fn quad(&mut self, points: [Vec3; 4], [u, v, w, h]: [f32; 4], texture: i32, color: u32) {
let uv = [[u, v], [u + w, v], [u + w, v + h], [u, v + h]];
self.quad_uv(points, uv, texture, color);
}
fn quad_uv(&mut self, points: [Vec3; 4], uv: [[f32; 2]; 4], texture: i32, color: u32) {
self.vertices.extend([0, 1, 2, 0, 2, 3].map(|i| Vertex {
position: points[i].to_array(),
uv: uv[i],
color,
texture,
}));
}
fn cuboid(
&mut self,
min: Vec3,
max: Vec3,
transform: impl Fn(Vec3) -> Vec3,
textures: [u8; 6],
brightness: [f32; 6],
) {
// `RenderBlocks.render*Face` corners in the order of the texture's top-left, top-right,
// bottom-right and bottom-left
const FACES: [[usize; 4]; 6] = [
[0, 1, 5, 4],
[3, 2, 6, 7],
[2, 3, 0, 1],
[7, 6, 5, 4],
[3, 7, 4, 0],
[6, 2, 1, 5],
];
let corners = box_corners(min, max).map(transform);
for (face, indices) in FACES.into_iter().enumerate() {
let shade = brightness[face] * [0.5, 1.0, 0.8, 0.8, 0.6, 0.6][face];
let points = indices.map(|i| corners[i]);
self.quad(points, tile_uv(textures[face]), 0, gray(shade));
}
}
/// Laid out glyphs with the top-left corner at `origin` in `transform`'s space.
fn glyphs(&mut self, glyphs: Vec<(u8, f32, u32)>, origin: Vec3, transform: glam::Mat4) {
for (ch, offset, color) in glyphs {
let (x, y) = (origin.x + offset, origin.y);
let points = [[x, y], [x + 7.99, y], [x + 7.99, y + 7.99], [x, y + 7.99]]
.map(|[x, y]| transform.transform_point3(Vec3::new(x, y, origin.z)));
let uv = crate::gui::Geometry::glyph_uv(ch);
self.quad(points, uv, crate::gui::FONT, color);
}
}
fn block_item(
&mut self,
block: common::world::Block,
metadata: u8,
transform: glam::Mat4,
brightness: f32,
) {
for face in super::item::faces(block, metadata) {
let color =
super::item::color(super::item::tint(block, metadata), brightness * face.shade);
let points = face.points.map(|p| transform.transform_point3(p));
self.quad_uv(points, face.uv, 0, color);
}
}
/// `TileEntityMobSpawnerRenderer`: a small spinning copy of each spawner's mob.
fn spawner_mobs(&mut self, world: &ClientWorld, eye: DVec3, partial: f32, sky: u32) {
let center = common::world::ChunkPos::containing(
common::math::floor_f64(eye.x),
common::math::floor_f64(eye.z),
);
for ([x, y, z], kind, _) in world.spawner_mobs_near(center, 4) {
let corner = DVec3::new(f64::from(x), f64::from(y), f64::from(z));
if (corner + 0.5).distance_squared(eye) >= 64.0 * 64.0 {
continue;
}
let (yaw, previous) = world
.spawner_yaws
.get(&[x, y, z])
.copied()
.unwrap_or_default();
let spin = ((previous + (yaw - previous) * f64::from(partial)) * 10.0) as f32;
let rotation = Mat3::from_rotation_y(spin.to_radians())
* Mat3::from_rotation_x((-30.0_f32).to_radians());
let origin = (corner - eye).as_vec3()
+ Vec3::new(0.5, 0.4, 0.5)
+ rotation * Vec3::new(0.0, -0.4, 0.0);
let brightness = brightness_at(world, corner + DVec3::new(0.5, 0.0, 0.5), sky);
let start = self.vertices.len();
let entity = crate::entities::RemoteEntity::new(0, Kind::Mob(kind), [0; 3]);
self.mob(kind, &entity, Vec3::ZERO, partial, brightness);
let transform = rotation * (7.0 / 16.0);
for vertex in &mut self.vertices[start..] {
vertex.position =
(origin + transform * Vec3::from_array(vertex.position)).to_array();
}
}
}
pub fn build(
&mut self,
entities: &Entities,
world: &ClientWorld,
player: &LocalPlayer,
interaction: &Interaction,
partial: f32,
sky_darkness: u32,
) {
self.vertices.clear();
self.anglers = entities
.0
.values()
.filter_map(|e| match e.kind {
Kind::Hook(owner) => owner,
_ => None,
})
.collect();
self.map = None;
self.map_text = 0..0;
self.map_transform = None;
self.portrait = 0..0;
self.weather = 0..0;
self.bolts = 0..0;
self.clouds = 0..0;
self.moving = 0..0;
let mut flashes = Vec::new();
let mut shadows = Vec::new();
let eye = player.eye(partial);
let view_yaw = lerp(player.entity.prev_yaw, player.entity.yaw, partial);
let billboard = Mat3::from_rotation_y(-player.entity.yaw.to_radians())
* Mat3::from_rotation_x(player.entity.pitch.to_radians());
if (player.third_person || player.entity.sleep.sleeping) && player.started {
let pos = player
.entity
.prev_pos
.lerp(player.entity.pos, f64::from(partial));
let feet = pos - DVec3::Y * f64::from(player.entity.y_offset);
let height = f64::from(player.entity.height) * 0.66;
if self.fancy {
shadows.push((feet, player.entity.height, 0.5, 1.0));
}
if player.entity.fire > 0 || player.entity.flag(common::entity::flags::ON_FIRE) {
self.entity_fire((feet - eye).as_vec3(), (0.6, 1.8, 0.0), view_yaw);
}
self.player_model(
&player.entity,
&interaction.inventory,
partial,
(feet - eye).as_vec3(),
brightness_at(world, feet + DVec3::Y * height, sky_darkness),
None,
player.bed_direction(world),
);
}
for (&id, entity) in &entities.0 {
let position = entity.previous.lerp(entity.position, f64::from(partial));
let (mut width, mut height, offset, shadow) = effects::dimensions(entity.kind);
if matches!(entity.kind, Kind::Mob(MobKind::Slime)) {
width *= f32::from(entity.flags[1].max(1));
height = width;
}
// `Entity.getEntityBrightness` samples two thirds up the body
let sample = match entity.kind {
Kind::Mob(_) | Kind::Player => f64::from(height) * 0.66,
_ => 0.0,
};
let brightness = brightness_at(world, position + DVec3::Y * sample, sky_darkness);
let relative = (position - eye).as_vec3();
if self.fancy {
// `RenderItem.shadowOpaque`
let opaque = if matches!(entity.kind, Kind::Item(_)) {
0.75
} else {
1.0
};
shadows.push((position, height, shadow, opaque));
}
if entity.flags[0] & common::entity::flags::ON_FIRE != 0 {
self.entity_fire(relative, (width, height, offset), view_yaw);
}
match entity.kind {
Kind::Player => {
if let Some(remote) = &entity.player {
let bed = remote.entity.sleep.bed.map(|[x, y, z]| {
usize::from(shape::BlockView::metadata(world, x, y, z) & 3)
});
// `RenderPlayer.renderPlayer` lowers other sneaking players
let sneak = if remote.entity.is_sneaking() {
0.125
} else {
0.0
};
self.player_model(
&remote.entity,
&remote.inventory,
partial,
relative - Vec3::Y * sneak,
brightness,
None,
bed,
);
}
}
Kind::Mob(kind) => self.mob(kind, entity, relative, partial, brightness),
Kind::Painting(painting) => self.painting(painting, relative, world, sky_darkness),
Kind::Vehicle(kind) => {
self.vehicle(world, kind, entity, relative, partial, brightness)
}
Kind::Hook(_) => self.hook(relative, &billboard),
Kind::Arrow => self.arrow(entity, relative, partial, brightness),
Kind::Tnt => {
let swelling = if entity.animation.fuse < 10 {
1.0 + (1.0 - (entity.animation.fuse as f32 - partial + 1.0) / 10.0)
.clamp(0.0, 1.0)
.powi(4)
* 0.3
} else {
1.0
};
self.cuboid(
Vec3::splat(-0.5),
Vec3::splat(0.5),
|p| p * swelling + relative,
std::array::from_fn(|side| {
texture::face(common::world::Block::TNT, 0, side).0
}),
[brightness; 6],
);
if entity.animation.fuse / 5 % 2 == 0 {
let fuse = entity.animation.fuse as f32 - partial + 1.0;
flashes.push((relative, swelling, (1.0 - fuse / 100.0) * 0.8));
}
}
Kind::Falling(block) => {
// `RenderBlocks.renderBlockFallingSand`: each face is at least as bright as
// its neighbor cell
let cell = position.floor();
let center = brightness_at(world, cell, sky_darkness);
self.cuboid(
Vec3::splat(-0.5),
Vec3::splat(0.5),
|p| p + relative,
std::array::from_fn(|side| texture::face(block, 0, side).0),
SIDES.map(|side| {
brightness_at(world, cell + side, sky_darkness).max(center)
}),
);
}
Kind::Thrown(_) | Kind::Fireball => {
let (item, scale) = match entity.kind {
Kind::Thrown(item) => (item, 1.0),
_ => (common::item::Item::SNOWBALL, 4.0),
};
let tile = item.properties().map_or(0, |p| p.icon);
let points = SPRITE.map(|p| billboard * p * scale + relative);
self.quad(points, tile_uv(tile), crate::gui::ITEMS, gray(brightness));
}
Kind::Item(stack) => {
let phase = (id as f32 * 0.618034).fract() * std::f32::consts::TAU;
let age = entity.age as f32 + partial;
let bob = (age / 10.0 + phase).sin() * 0.1 + 0.1;
let position = relative + Vec3::Y * bob;
let copies = match stack.count {
21.. => 4,
6.. => 3,
2.. => 2,
_ => 1,
};
let mut random = common::JavaRandom::new(187);
for i in 0..copies {
let mut scatter = |spread: f32| {
if i == 0 {
Vec3::ZERO
} else {
Vec3::from_array(std::array::from_fn(|_| {
(random.next_float() * 2.0 - 1.0) * spread
}))
}
};
if let Some(block) = stack.item.block()
&& super::item::in_3d(block)
{
let rotation = glam::Mat4::from_rotation_y(age / 20.0 + phase);
let scale = if block.properties().is_some_and(|p| {
p.normal || p.shape == common::world::Shape::PistonBase
}) || block == common::world::Block::SLAB
{
0.25
} else {
0.5
};
let transform = glam::Mat4::from_translation(position)
* rotation
* glam::Mat4::from_scale(Vec3::splat(scale))
* glam::Mat4::from_translation(scatter(0.2 / scale));
self.block_item(block, stack.damage as u8, transform, brightness);
} else {
let (atlas, tile) = stack.item.block().map_or_else(
|| (crate::gui::ITEMS, super::item::icon(stack)),
|block| (0, texture::inventory_face(block, stack.damage as u8, 0)),
);
let rotation = Mat3::from_rotation_y(-player.entity.yaw.to_radians());
let offset = scatter(0.3) * 0.5 + position;
let points = SPRITE.map(|p| rotation * p + offset);
self.quad(points, tile_uv(tile), atlas, gray(brightness));
}
}
}
}
}
self.spawner_mobs(world, eye, partial, sky_darkness);
for particle in &world.particles.particles {
let position = particle
.previous
.lerp(particle.position, f64::from(partial));
let look = particle.appearance(partial);
let world_light = brightness_at(world, position, sky_darkness);
let light = look.light.apply(world_light);
let center = (position - eye).as_vec3();
if let Some(alpha) = look.flat_alpha {
let half = look.size;
let shade = (world_light * 255.0) as u32;
self.quad(
[
center + Vec3::new(-half, 0.0, -half),
center + Vec3::new(half, 0.0, -half),
center + Vec3::new(half, 0.0, half),
center + Vec3::new(-half, 0.0, half),
],
look.uv,
look.texture,
(((alpha * 255.0) as u32) << 24) | (shade * 0x010101),
);
continue;
}
let color = (particle.color * light * 255.0).as_uvec3();
let right = billboard.x_axis * look.size;
let up = billboard.y_axis * look.size;
self.quad(
[
center - right + up,
center + right + up,
center + right - up,
center - right - up,
],
look.uv,
look.texture,
0xff000000 | color.x | (color.y << 8) | (color.z << 16),
);
}
self.objects = 0..self.vertices.len() as u32;
let start = self.vertices.len() as u32;
self.fishing_lines(entities, player, partial);
self.lines = start..self.vertices.len() as u32;
for (relative, swelling, alpha) in flashes {
let start = self.vertices.len();
self.cuboid(
Vec3::splat(-0.5),
Vec3::splat(0.5),
|p| p * swelling + relative,
[0; 6],
[1.0; 6],
);
let color = ((alpha.clamp(0.0, 1.0) * 255.0) as u32) << 24 | 0xffffff;
for vertex in &mut self.vertices[start..] {
vertex.texture = -1;
vertex.color = color;
}
}
self.vertices.append(&mut self.auras);
self.flashes = self.lines.end..self.vertices.len() as u32;
for (position, height, size, opaque) in shadows {
self.shadow(world, position, eye, (height, size, opaque), sky_darkness);
}
self.shadows = self.flashes.end..self.vertices.len() as u32;
self.outline = self.shadows.end..self.shadows.end;
self.cracks = self.shadows.end..self.shadows.end;
if interaction.screen.is_none()
&& let Some(hit) = interaction.shown
{
let block = shape::BlockView::block(world, hit.x, hit.y, hit.z);
let bounds = shape::selection_box(world, block, hit.x, hit.y, hit.z)
.inflate(DVec3::splat(0.002));
let min = (bounds.min - eye).as_vec3();
let max = (bounds.max - eye).as_vec3();
for axis in 0..3 {
let other = [(axis + 1) % 3, (axis + 2) % 3];
for a in [false, true] {
for b in [false, true] {
let mut start = min;
start[other[0]] = if a { max[other[0]] } else { min[other[0]] };
start[other[1]] = if b { max[other[1]] } else { min[other[1]] };
let mut end = start;
end[axis] = max[axis];
let direction = (end - start).normalize_or_zero();
let width =
direction.cross((start + end) * 0.5).normalize_or_zero() * 0.0015;
self.quad(
[start - width, start + width, end + width, end - width],
[0.0; 4],
-1,
0x66000000,
);
}
}
}
self.outline.end = self.vertices.len() as u32;
self.cracks = self.outline.end..self.outline.end;
let damage = interaction.damage(partial);
let column = common::world::ChunkPos::containing(hit.x, hit.z);
if damage > 0.0
&& let Some(input) = world.mesh_input(column, hit.y >> 4, self.fancy, false)
{
let tile = 240 + (damage * 10.0).min(9.0) as u8;
let origin = DVec3::new(
f64::from(column.min_block_x()),
f64::from(hit.y & !15),
f64::from(column.min_block_z()),
) - eye;
let quads = crate::render::mesher::overlay(
&input,
[hit.x & 15, hit.y & 15, hit.z & 15],
tile,
);
for quad in &quads {
let quad = &quad.vertices;
let points = quad.each_ref().map(|vertex| {
let position = (origin + vertex.position().as_dvec3()).as_vec3();
// `glPolygonOffset(-3, -3)`: drawn just in front of the block
position - position.normalize_or_zero() * 0.002
});
self.quad_uv(points, quad.each_ref().map(|v| v.uv()), 0, 0xffffffff);
}
self.cracks.end = self.vertices.len() as u32;
}
}
self.hand = self.vertices.len() as u32..self.vertices.len() as u32;
if !player.third_person && !player.entity.sleep.sleeping && player.started {
let brightness = brightness_at(world, eye, sky_darkness);
if !self.hide_hand && player.entity.living.as_ref().is_some_and(|l| l.health > 0) {
self.first_person(player, interaction, world, partial, brightness);
}
self.hazard_overlays(player, world, brightness);
}
self.hand.end = self.vertices.len() as u32;
}
}