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 mesher devices.rs
//! Beds, levers, repeaters, redstone wire, rails and pistons: the special `RenderBlocks`
//! shapes of mechanisms.
use common::math;
use common::world::shape::SIDE_OFFSETS;
use common::world::{Block, bed, redstone, texture};
use glam::{DVec3, Vec3};
use super::block::{self, Bounds, Orientation};
use super::special::torch_at;
use super::{Builder, Cache, Light, MeshInput, Shade, WHITE, tile};
/// The light at a block with its own emission as the minimum (`Block.getBlockBrightness`).
fn brightness(cache: &Cache, block: Block, [x, y, z]: [i32; 3]) -> Light {
cache.light(x, y, z).with_emission(block.light_emission())
}
// ---- Beds -------------------------------------------------------
/// `ModelBed.headInvisibleFace`: the side where the other half joins.
const HEAD_INVISIBLE_FACES: [usize; 4] = [3, 4, 2, 5];
/// `renderBlockBed`: flat-lit faces, the pillow top turned by the bed's direction.
pub fn bed(input: &MeshInput, cache: &Cache, builder: &mut Builder, position: [i32; 3]) {
let [x, y, z] = position;
let block = Block::BED;
let metadata = cache.metadata(x, y, z);
let direction = bed::direction(metadata);
let bounds = Bounds::with_height(9.0 / 16.0);
let own = brightness(cache, block, position);
let face = |side| texture::face(block, metadata, side).0;
let full = |texture: u8| {
let (tx, ty) = (f64::from(texture & 15) * 16.0, f64::from(texture & 0xF0));
let range = |t: f64| (f64::from(t as f32 / 256.0), (t + 16.0 - 0.01) / 256.0);
let ((u0, u1), (v0, v1)) = (range(tx), range(ty));
(u0, u1, v0, v1)
};
let uv = |u: f64, v: f64| (u as f32, v as f32);
let (u0, u1, v0, v1) = full(face(0));
let floor = 3.0 / 16.0;
builder.quad(
[
(Vec3::new(0.0, floor, 1.0), uv(u0, v1)),
(Vec3::new(0.0, floor, 0.0), uv(u0, v0)),
(Vec3::new(1.0, floor, 0.0), uv(u1, v0)),
(Vec3::new(1.0, floor, 1.0), uv(u1, v1)),
],
own,
Shade::Bottom,
WHITE,
);
let (u0, u1, v0, v1) = full(face(1));
// Corners (1, 1), (1, 0), (0, 0) and (0, 1) of the top, as (x, z)
let uvs = match direction {
0 => [(u1, v1), (u0, v1), (u0, v0), (u1, v0)],
2 => [(u0, v0), (u1, v0), (u1, v1), (u0, v1)],
3 => [(u1, v0), (u1, v1), (u0, v1), (u0, v0)],
_ => [(u0, v1), (u0, v0), (u1, v0), (u1, v1)],
};
let quad = [0b111, 0b110, 0b010, 0b011].map(|corner| bounds.corner(corner));
let quad = std::array::from_fn(|i| (quad[i], uv(uvs[i].0, uvs[i].1)));
let above = brightness(cache, block, [x, y + 1, z]);
builder.quad(quad, above, Shade::Top, WHITE);
let hidden = if bed::is_head(metadata) {
HEAD_INVISIBLE_FACES[bed::FOOT_INVISIBLE_FACES[direction] as usize]
} else {
HEAD_INVISIBLE_FACES[direction]
};
let flipped = match direction {
0 => 5,
1 => 3,
3 => 2,
_ => 4,
};
for (side, [dx, dy, dz]) in SIDE_OFFSETS.into_iter().enumerate().skip(2) {
if side == hidden
|| !block::side_visible(cache, block, position, side, &bounds, input.fancy)
{
continue;
}
let orientation = Orientation {
rotate: [0; 6],
flip: side == flipped,
};
let quad = block::oriented_face_quad(side, &bounds, face(side), orientation);
let light = brightness(cache, block, [x + dx, y + dy, z + dz]);
builder.quad(quad, light, block::shade(side), WHITE);
}
}
// ---- Levers -----------------------------------------------------
/// `renderBlockLever`: a cobblestone base and a handle tilted by the lever's state.
pub fn lever(input: &MeshInput, cache: &Cache, builder: &mut Builder, position: [i32; 3]) {
let [x, y, z] = position;
let metadata = cache.metadata(x, y, z);
let orientation = metadata & 7;
let on = metadata & 8 != 0;
let (long, wide, deep) = (0.25, 3.0 / 16.0, 3.0 / 16.0);
let (min, max) = match orientation {
5 => (
[0.5 - wide, 0.0, 0.5 - long],
[0.5 + wide, deep, 0.5 + long],
),
6 => (
[0.5 - long, 0.0, 0.5 - wide],
[0.5 + long, deep, 0.5 + wide],
),
4 => (
[0.5 - wide, 0.5 - long, 1.0 - deep],
[0.5 + wide, 0.5 + long, 1.0],
),
3 => (
[0.5 - wide, 0.5 - long, 0.0],
[0.5 + wide, 0.5 + long, deep],
),
2 => (
[1.0 - deep, 0.5 - long, 0.5 - wide],
[1.0, 0.5 + long, 0.5 + wide],
),
_ => (
[0.0, 0.5 - long, 0.5 - wide],
[deep, 0.5 + long, 0.5 + wide],
),
};
// The base is drawn like a cobblestone block of the lever's bounds
let bounds = Bounds::new(min, max);
block::standard_bounds(input, cache, builder, Block::COBBLESTONE, position, bounds);
let rotate_x = |v: DVec3, angle: f32| {
let (c, s) = (f64::from(math::cos(angle)), f64::from(math::sin(angle)));
DVec3::new(v.x, v.y * c + v.z * s, v.z * c - v.y * s)
};
let rotate_y = |v: DVec3, angle: f32| {
let (c, s) = (f64::from(math::cos(angle)), f64::from(math::sin(angle)));
DVec3::new(v.x * c + v.z * s, v.y, v.z * c - v.x * s)
};
let (half, height) = (1.0 / 16.0, 10.0 / 16.0);
let pi = std::f32::consts::PI;
let tilt = pi * 2.0 / 9.0;
// The bottom corners counterclockwise from (-x, -z), then the top ones
let corners: [Vec3; 8] = std::array::from_fn(|i| {
let x = if matches!(i % 4, 1 | 2) { half } else { -half };
let z = if i % 4 >= 2 { half } else { -half };
let y = if i >= 4 { height } else { 0.0 };
let mut v = DVec3::new(
f64::from(x as f32),
f64::from(y as f32),
f64::from(z as f32),
);
if on {
v.z -= 1.0 / 16.0;
v = rotate_x(v, tilt);
} else {
v.z += 1.0 / 16.0;
v = rotate_x(v, -tilt);
}
if orientation == 6 {
v = rotate_y(v, pi * 0.5);
}
if orientation < 5 {
v.y -= 0.375;
v = rotate_x(v, pi * 0.5);
v = match orientation {
3 => rotate_y(v, pi),
2 => rotate_y(v, pi * 0.5),
1 => rotate_y(v, pi * -0.5),
_ => rotate_y(v, 0.0),
};
v + DVec3::new(0.5, 0.5, 0.5)
} else {
v + DVec3::new(0.5, f64::from(2.0_f32 / 16.0), 0.5)
}
.as_vec3()
});
let texture = Block::LEVER.properties().unwrap().texture;
let (tx, ty) = (f32::from(texture & 15) * 16.0, f32::from(texture & 0xF0));
let (u0, u1) = ((tx + 7.0) / 256.0, (tx + 9.0 - 0.01) / 256.0);
let light = brightness(cache, Block::LEVER, position);
for (face, indices) in [
[0, 1, 2, 3],
[7, 6, 5, 4],
[1, 0, 4, 5],
[2, 1, 5, 6],
[3, 2, 6, 7],
[0, 3, 7, 4],
]
.into_iter()
.enumerate()
{
let bottom = if face < 2 { 8.0 } else { 16.0 };
let (v0, v1) = ((ty + 6.0) / 256.0, (ty + bottom - 0.01) / 256.0);
let [a, b, c, d] = indices.map(|i| corners[i]);
builder.quad(
[(a, (u0, v1)), (b, (u1, v1)), (c, (u1, v0)), (d, (u0, v0))],
light,
Shade::Top,
WHITE,
);
}
}
// ---- Repeaters --------------------------------------------------
/// `BlockRedstoneRepeater.repeaterTorchOffset`: how far the movable torch sits for each delay.
const REPEATER_TORCH_OFFSETS: [f32; 4] = [-0.0625, 1.0 / 16.0, 0.1875, 0.3125];
/// `renderBlockRepeater`: a slab with two torches and the arrow on top.
pub fn repeater(
input: &MeshInput,
cache: &Cache,
builder: &mut Builder,
block: Block,
position: [i32; 3],
) {
let [x, y, z] = position;
let metadata = cache.metadata(x, y, z);
let direction = metadata & 3;
let delay = usize::from((metadata & 12) >> 2);
let bounds = Bounds::with_height(2.0 / 16.0);
block::standard_bounds(input, cache, builder, block, position, bounds);
let light = brightness(cache, block, position);
let shade = if block.light_emission() > 0 {
Shade::Glow
} else {
Shade::Top
};
let offset = REPEATER_TORCH_OFFSETS[delay];
let (first, second) = match direction {
0 => ((0.0, offset), (0.0, -0.3125)),
1 => ((-offset, 0.0), (0.3125, 0.0)),
2 => ((0.0, -offset), (0.0, 0.3125)),
_ => ((offset, 0.0), (-0.3125, 0.0)),
};
let torch = texture::face(block, metadata, 0).0;
for (dx, dz) in [first, second] {
let offset = Vec3::new(dx, -0.1875, dz);
torch_at(builder, offset, Vec3::ZERO, torch, light, shade);
}
let [u0, u1, v0, v1] = tile(texture::face(block, metadata, 1).0);
let (x0, x1, z0, z1) = (0.0, 1.0, 0.0, 1.0);
let corners = match direction {
2 => [(x1, z1), (x1, z0), (x0, z0), (x0, z1)],
3 => [(x0, z1), (x1, z1), (x1, z0), (x0, z0)],
1 => [(x1, z0), (x0, z0), (x0, z1), (x1, z1)],
_ => [(x0, z0), (x0, z1), (x1, z1), (x1, z0)],
};
let top = 2.0 / 16.0;
let uvs = [(u0, v0), (u0, v1), (u1, v1), (u1, v0)];
let quad = std::array::from_fn(|i| (Vec3::new(corners[i].0, top, corners[i].1), uvs[i]));
builder.quad(quad, light, shade, WHITE);
}
// ---- Redstone wire ----------------------------------------------
/// `renderBlockRedstoneWire`: dust tinted by its power, with an untinted overlay, running up
/// the sides of blocks it climbs.
pub fn wire(cache: &Cache, builder: &mut Builder, position: [i32; 3]) {
let [x, y, z] = position;
let power = cache.metadata(x, y, z);
let level = f32::from(power) / 15.0;
let red = if power == 0 { 0.3 } else { level * 0.6 + 0.4 };
let green = (level * level * 0.7 - 0.5).max(0.0);
let blue = (level * level * 0.6 - 0.7).max(0.0);
let channel = |v: f32| (v * 255.0) as u32;
let rgb = channel(red) << 16 | channel(green) << 8 | channel(blue);
let light = brightness(cache, Block::REDSTONE_WIRE, position);
let texture = Block::REDSTONE_WIRE.properties().unwrap().texture;
let (tx, ty) = (f32::from(texture & 15) * 16.0, f32::from(texture & 0xF0));
let range = |base: f32| (f64::from(base / 256.0), f64::from((base + 15.99) / 256.0));
let ((mut u0, mut u1), (mut v0, mut v1)) = (range(tx), range(ty));
let [west, east, north, south] = redstone::links(cache, x, y, z);
let line = match (west || east, north || south) {
(true, false) => 1,
(false, true) => 2,
_ => 0,
};
if line != 0 {
((u0, u1), (v0, v1)) = (range(tx + 16.0), range(ty));
}
let (mut x0, mut x1, mut z0, mut z1) = (0.0_f32, 1.0_f32, 0.0_f32, 1.0_f32);
if line == 0 && (west || east || north || south) {
let inset = 5.0 / 16.0;
let shift = 1.25 / 64.0;
if !west {
x0 += inset;
u0 += shift;
}
if !east {
x1 -= inset;
u1 -= shift;
}
if !north {
z0 += inset;
v0 += shift;
}
if !south {
z1 -= inset;
v1 -= shift;
}
}
let height = 0.015625;
let uvs = if line == 2 {
[(u1, v1), (u0, v1), (u0, v0), (u1, v0)]
} else {
[(u1, v1), (u1, v0), (u0, v0), (u0, v1)]
};
let points = [(x1, z1), (x1, z0), (x0, z0), (x0, z1)].map(|(px, pz)| Vec3::new(px, height, pz));
dust(builder, points, uvs, light, rgb);
if cache.block(x, y + 1, z).is_normal_cube() {
return;
}
let ((u0, u1), (v0, v1)) = (range(tx + 16.0), range(ty));
let (top, edge) = (1.0 + 7.0 / 320.0, 0.015625);
let climbs = |dx: i32, dz: i32| {
cache.block(x + dx, y, z + dz).is_normal_cube()
&& cache.block(x + dx, y + 1, z + dz) == Block::REDSTONE_WIRE
};
let (down, up) = (
[(u1, v0), (u0, v0), (u0, v1), (u1, v1)],
[(u0, v1), (u1, v1), (u1, v0), (u0, v0)],
);
for (dx, dz) in [(-1, 0), (1, 0), (0, -1), (0, 1)] {
if !climbs(dx, dz) {
continue;
}
// West and south walls start at the top, east and north ones at the bottom
let descends = dx < 0 || dz > 0;
let wall = if dx + dz < 0 { edge } else { 1.0 - edge };
let points = std::array::from_fn(|i| {
let along = if i < 2 { 1.0 } else { 0.0 };
let y = if descends == (i % 3 == 0) { top } else { 0.0 };
if dx != 0 {
Vec3::new(wall, y, along)
} else {
Vec3::new(along, y, wall)
}
});
dust(
builder,
points,
if descends { down } else { up },
light,
rgb,
);
}
}
/// One wire quad: the dust in its power's color, then its plain overlay a tile further down.
fn dust(builder: &mut Builder, points: [Vec3; 4], uvs: [(f64, f64); 4], light: Light, rgb: u32) {
let quad =
|dv: f64| std::array::from_fn(|i| (points[i], (uvs[i].0 as f32, (uvs[i].1 + dv) as f32)));
builder.quad(quad(0.0), light, Shade::Top, rgb);
builder.mark_cutout();
builder.quad(quad(1.0 / 16.0), light, Shade::Top, WHITE);
}
// ---- Rails ------------------------------------------------------
/// `renderBlockMinecartTrack`: a two-sided plane, raised at one end on slopes.
pub fn rail(cache: &Cache, builder: &mut Builder, block: Block, position: [i32; 3]) {
let [x, y, z] = position;
let mut metadata = cache.metadata(x, y, z);
let [u0, u1, v0, v1] = tile(texture::face(block, metadata, 0).0);
if block != Block::RAIL {
metadata &= 7;
}
let (x0, x1, z0, z1) = (0.0, 1.0, 0.0, 1.0);
let corners = match metadata {
1 | 2 | 3 | 7 => [(x1, z1), (x0, z1), (x0, z0), (x1, z0)],
8 => [(x0, z1), (x0, z0), (x1, z0), (x1, z1)],
9 => [(x0, z0), (x1, z0), (x1, z1), (x0, z1)],
_ => [(x1, z0), (x1, z1), (x0, z1), (x0, z0)],
};
let (low, high) = (1.0 / 16.0, 1.0 / 16.0 + 1.0);
let heights = match metadata {
2 | 4 => [high, low, low, high],
3 | 5 => [low, high, high, low],
_ => [low; 4],
};
let uvs = [(u1, v0), (u1, v1), (u0, v1), (u0, v0)];
let quad: [(Vec3, (f32, f32)); 4] =
std::array::from_fn(|i| (Vec3::new(corners[i].0, heights[i], corners[i].1), uvs[i]));
let light = brightness(cache, block, position);
builder.quad(quad, light, Shade::Top, WHITE);
let [a, b, c, d] = quad;
builder.quad([d, c, b, a], light, Shade::Top, WHITE);
}
// ---- Pistons ----------------------------------------------------
/// The `uvRotate*` of `renderPistonBase` and `renderPistonExtension` for each facing, by face.
const PISTON_ROTATIONS: [[u8; 6]; 6] = [
[0, 0, 3, 3, 3, 3],
[0; 6],
[0, 0, 0, 0, 2, 1],
[3, 3, 0, 0, 1, 2],
[1, 2, 1, 2, 0, 0],
[2, 1, 2, 1, 0, 0],
];
fn facing(metadata: u8) -> usize {
usize::from(metadata & 7)
}
/// The part of a block a quarter block thick towards (`towards`) or away from a facing.
fn plate(facing: usize, towards: bool) -> Bounds {
let (mut min, mut max) = (Vec3::ZERO, Vec3::ONE);
let axis = [1, 1, 2, 2, 0, 0][facing];
let positive = facing % 2 == 1;
match (positive, towards) {
(false, true) => max[axis] = 0.25,
(true, true) => min[axis] = 0.75,
(false, false) => min[axis] = 0.25,
(true, false) => max[axis] = 0.75,
}
Bounds { min, max }
}
/// `renderPistonBase`: a full block, or without the head's quarter while extended.
pub fn piston_base(
input: &MeshInput,
cache: &Cache,
builder: &mut Builder,
block: Block,
position: [i32; 3],
) {
let [x, y, z] = position;
let metadata = cache.metadata(x, y, z);
let facing = facing(metadata);
if facing > 5 {
return block::standard(input, cache, builder, block, position);
}
let bounds = if metadata & 8 != 0 {
plate(facing, false)
} else {
Bounds::UNIT
};
block::standard_oriented(
input,
cache,
builder,
block,
position,
bounds,
PISTON_ROTATIONS[facing],
);
}
/// `renderPistonExtension`: the head plate and the four sides of its rod.
pub fn piston_extension(
input: &MeshInput,
cache: &Cache,
builder: &mut Builder,
position: [i32; 3],
(length, shift): (f32, f32),
) {
let [x, y, z] = position;
let block = Block::PISTON_HEAD;
let facing = facing(cache.metadata(x, y, z));
if facing > 5 {
return;
}
block::standard_oriented(
input,
cache,
builder,
block,
position,
plate(facing, true),
PISTON_ROTATIONS[facing],
);
let light = brightness(cache, block, position);
piston_rod(builder, facing, length, shift, light);
}
/// The rod quads of `renderPistonExtension`, `length` long and showing `shift` texels of the
/// rod texture.
fn piston_rod(builder: &mut Builder, facing: usize, length: f32, shift: f32, light: Light) {
let (a, b) = (6.0 / 16.0, 10.0 / 16.0);
let (start, end) = if facing.is_multiple_of(2) {
(0.25, 0.25 + length)
} else {
(0.75 - length, 0.75)
};
use Shade::{Bottom, Top, XSide, ZSide};
// Per rod side: x1, x2, y1, y2, z1, z2 (the rod's own axis spans start..end), and shading
let rods: [([f32; 6], Shade); 4] = match facing {
0 | 1 => [
([a, b, start, end, b, b], ZSide),
([b, a, start, end, a, a], ZSide),
([a, a, start, end, a, b], XSide),
([b, b, start, end, b, a], XSide),
],
2 | 3 => [
([a, a, b, a, start, end], XSide),
([b, b, a, b, start, end], XSide),
([a, b, a, a, start, end], Bottom),
([b, a, b, b, start, end], Top),
],
_ => [
([start, end, a, a, b, a], Bottom),
([start, end, b, b, a, b], Top),
([start, end, a, b, a, a], XSide),
([start, end, b, a, b, b], XSide),
],
};
let texture = 108_u8;
let (tx, ty) = (f32::from(texture & 15) * 16.0, f32::from(texture & 0xF0));
let (u0, v0) = (tx / 256.0, ty / 256.0);
let u1 = ((f64::from(tx) + f64::from(shift) - 0.01) / 256.0) as f32;
let v1 = ((f64::from(ty + 4.0) - 0.01) / 256.0) as f32;
let p = Vec3::new;
for ([x1, x2, y1, y2, z1, z2], shade) in rods {
let points = match facing {
0 | 1 => [p(x1, y2, z1), p(x1, y1, z1), p(x2, y1, z2), p(x2, y2, z2)],
2 | 3 => [p(x1, y1, z2), p(x1, y1, z1), p(x2, y2, z1), p(x2, y2, z2)],
_ => [p(x2, y1, z1), p(x1, y1, z1), p(x1, y2, z2), p(x2, y2, z2)],
};
let uvs = [(u1, v0), (u0, v0), (u0, v1), (u1, v1)];
let quad = std::array::from_fn(|i| (points[i], uvs[i]));
builder.quad(quad, light, shade, WHITE);
}
}