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 special.rs
use common::physics::Aabb;
use common::world::{Block, Shape, shape};
use glam::{DVec3, Vec3};
use super::block::{self, Bounds};
use super::{Builder, Cache, Light, MeshInput, Shade, WHITE, tile};
pub fn render(
input: &MeshInput,
cache: &Cache,
builder: &mut Builder,
block: Block,
position: [i32; 3],
) {
let [x, y, z] = position;
let light = cache.light(x, y, z);
let mut cuboid =
|bounds| block::standard_bounds(input, cache, builder, block, position, bounds);
match block.properties().map(|p| p.shape) {
Some(Shape::Fire) => fire(
cache,
builder,
position,
block.properties().unwrap().texture,
),
Some(Shape::Stairs) => {
let mut boxes = Vec::with_capacity(2);
let origin = DVec3::new(f64::from(x), f64::from(y), f64::from(z));
let region = Aabb::new(origin, origin + DVec3::ONE);
shape::collision_boxes(cache, block, x, y, z, ®ion, &mut boxes);
for bounds in boxes {
cuboid(Bounds::of(&bounds.offset(-origin)));
}
}
Some(Shape::Door) => cuboid(Bounds::of(&shape::block_bounds(cache, block, x, y, z))),
Some(Shape::Fence) => {
cuboid(Bounds::new([0.375, 0.0, 0.375], [0.625, 1.0, 0.625]));
let west = cache.block(x - 1, y, z) == block;
let east = cache.block(x + 1, y, z) == block;
let north = cache.block(x, y, z - 1) == block;
let south = cache.block(x, y, z + 1) == block;
let default_x = !west && !east && !north && !south;
for bottom in [0.375, 0.75] {
if west || east || default_x {
cuboid(Bounds::new(
[if west { 0.0 } else { 0.4375 }, bottom, 0.4375],
[if east { 1.0 } else { 0.5625 }, bottom + 0.1875, 0.5625],
));
}
if north || south {
cuboid(Bounds::new(
[0.4375, bottom, if north { 0.0 } else { 0.4375 }],
[0.5625, bottom + 0.1875, if south { 1.0 } else { 0.5625 }],
));
}
}
}
// `RenderBlocks.renderBlockLadder`
Some(Shape::Ladder) => {
let [u0, u1, v0, v1] = tile(block::texture(cache, block, position, 0, true));
let corners = match cache.metadata(x, y, z) {
5 => [
([0.05, 1.0, 1.0], (u0, v0)),
([0.05, 0.0, 1.0], (u0, v1)),
([0.05, 0.0, 0.0], (u1, v1)),
([0.05, 1.0, 0.0], (u1, v0)),
],
4 => [
([0.95, 0.0, 1.0], (u1, v1)),
([0.95, 1.0, 1.0], (u1, v0)),
([0.95, 1.0, 0.0], (u0, v0)),
([0.95, 0.0, 0.0], (u0, v1)),
],
3 => [
([1.0, 0.0, 0.05], (u1, v1)),
([1.0, 1.0, 0.05], (u1, v0)),
([0.0, 1.0, 0.05], (u0, v0)),
([0.0, 0.0, 0.05], (u0, v1)),
],
2 => [
([1.0, 1.0, 0.95], (u0, v0)),
([1.0, 0.0, 0.95], (u0, v1)),
([0.0, 0.0, 0.95], (u1, v1)),
([0.0, 1.0, 0.95], (u1, v0)),
],
_ => return,
};
let quad = corners.map(|(p, uv)| (Vec3::from_array(p), uv));
builder.quad(quad, light, Shade::Top, WHITE);
}
Some(Shape::Crops) => {
let texture = block::texture(cache, block, position, 0, true);
let y = -0.0625;
for offset in [0.25, 0.75] {
plane(builder, [offset, y, 0.0], [offset, y, 1.0], texture, light);
plane(builder, [0.0, y, offset], [1.0, y, offset], texture, light);
}
}
_ => {}
}
}
/// `RenderBlocks.renderBlockFire`: flames clinging to burnable walls and ceilings, or rising
/// from the floor.
fn fire(cache: &Cache, builder: &mut Builder, [x, y, z]: [i32; 3], texture: u8) {
let light = Light { sky: 15, block: 15 };
let burns = |dx: i32, dy: i32, dz: i32| cache.block(x + dx, y + dy, z + dz).fire_rates().0 > 0;
let [u0, u1, v0, v1] = tile(texture);
let [w0, w1] = [v0, v1].map(|v| v + 16.0 / 256.0);
// A quad from (x, z) corners, the outer two at height `y0` and the inner two at `y1`
let mut quad = |xz: [(f32, f32); 4], (y0, y1), (ua, ub), (va, vb)| {
let corners = [(y0, ua, va), (y1, ua, vb), (y1, ub, vb), (y0, ub, va)];
let quad = std::array::from_fn(|i| {
let ((x, z), (y, u, v)) = (xz[i], corners[i]);
(Vec3::new(x, y, z), (u, v))
});
builder.quad(quad, light, Shade::Top, WHITE);
};
let below = cache.block(x, y - 1, z);
if !below.is_normal_cube() && !burns(0, -1, 0) {
let (h, low, side) = (1.4 + 1.0 / 16.0, 1.0 / 16.0, 0.2);
let (mut a0, mut a1, mut b0, mut b1) = (u0, u1, v0, v1);
if (x + y + z) & 1 == 1 {
(b0, b1) = (w0, w1);
}
if (x / 2 + y / 2 + z / 2) & 1 == 1 {
(a0, a1) = (a1, a0);
}
let walls = [
[(side, 1.0), (0.0, 1.0), (0.0, 0.0), (side, 0.0)],
[(1.0 - side, 0.0), (1.0, 0.0), (1.0, 1.0), (1.0 - side, 1.0)],
[(0.0, side), (0.0, 0.0), (1.0, 0.0), (1.0, side)],
[(1.0, 1.0 - side), (1.0, 1.0), (0.0, 1.0), (0.0, 1.0 - side)],
];
let sides = [(-1, 0), (1, 0), (0, -1), (0, 1)];
for (i, ((dx, dz), [p, q, r, s])) in sides.into_iter().zip(walls).enumerate() {
if burns(dx, 0, dz) {
let us = if i % 2 == 0 { (a1, a0) } else { (a0, a1) };
quad([p, q, r, s], (h, low), us, (b0, b1));
quad([s, r, q, p], (h, low), (us.1, us.0), (b0, b1));
}
}
if burns(0, 1, 0) {
// Two slopes, turned by the block's parity
let (top, tip) = (1.0, 0.8);
let square = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)];
let turn = if (x + y + 1 + z) & 1 == 0 { 0 } else { 3 };
for (turn, vs) in [(turn, (v0, v1)), (turn + 2, (w0, w1))] {
let xz = std::array::from_fn(|i| square[(i + turn) % 4]);
quad(xz, (tip, top), (u1, u0), vs);
}
}
return;
}
let h = 1.4;
let planes = [
[(0.2, 1.0), (0.7, 1.0), (0.7, 0.0), (0.2, 0.0)],
[(0.8, 0.0), (0.3, 0.0), (0.3, 1.0), (0.8, 1.0)],
[(1.0, 0.8), (1.0, 0.3), (0.0, 0.3), (0.0, 0.8)],
[(0.0, 0.2), (0.0, 0.7), (1.0, 0.7), (1.0, 0.2)],
[(0.1, 0.0), (0.0, 0.0), (0.0, 1.0), (0.1, 1.0)],
[(0.9, 1.0), (1.0, 1.0), (1.0, 0.0), (0.9, 0.0)],
[(0.0, 0.9), (0.0, 1.0), (1.0, 1.0), (1.0, 0.9)],
[(1.0, 0.1), (1.0, 0.0), (0.0, 0.0), (0.0, 0.1)],
];
for (i, xz) in planes.into_iter().enumerate() {
let us = if i < 4 { (u1, u0) } else { (u0, u1) };
let vs = if (2..6).contains(&i) {
(w0, w1)
} else {
(v0, v1)
};
quad(xz, (h, 0.0), us, vs);
}
}
/// A double-sided plane from `a` to `b`, a block high.
fn plane(builder: &mut Builder, a: [f32; 3], b: [f32; 3], texture: u8, light: Light) {
let (a, b) = (Vec3::from_array(a), Vec3::from_array(b));
let (u, v) = (
f32::from(texture & 15) / 16.0,
f32::from(texture >> 4) / 16.0,
);
let edge = 15.99 / 256.0;
let [p, q, r, s] = [
(a + Vec3::Y, (u, v)),
(a, (u, v + edge)),
(b, (u + edge, v + edge)),
(b + Vec3::Y, (u + edge, v)),
];
builder.quad([p, q, r, s], light, Shade::Top, WHITE);
builder.quad([s, r, q, p], light, Shade::Top, WHITE);
}
pub fn torch(cache: &Cache, builder: &mut Builder, block: Block, [x, y, z]: [i32; 3]) {
let (offset, tilt) = match cache.metadata(x, y, z) {
1 => (Vec3::new(-0.1, 0.2, 0.0), Vec3::new(-0.4, 0.0, 0.0)),
2 => (Vec3::new(0.1, 0.2, 0.0), Vec3::new(0.4, 0.0, 0.0)),
3 => (Vec3::new(0.0, 0.2, -0.1), Vec3::new(0.0, 0.0, -0.4)),
4 => (Vec3::new(0.0, 0.2, 0.1), Vec3::new(0.0, 0.0, 0.4)),
_ => (Vec3::ZERO, Vec3::ZERO),
};
let light = if block.light_emission() > 0 {
Light { sky: 15, block: 15 }
} else {
cache.light(x, y, z)
};
let texture = block.properties().unwrap().texture;
torch_at(builder, offset, tilt, texture, light, Shade::Top);
}
/// `renderTorchAtAngle`: a torch standing at `offset`, its foot pushed out by `tilt`.
pub fn torch_at(
builder: &mut Builder,
offset: Vec3,
tilt: Vec3,
texture: u8,
light: Light,
shade: Shade,
) {
let (u, v) = (
f32::from(texture & 15) / 16.0,
f32::from(texture >> 4) / 16.0,
);
let center = offset + Vec3::new(0.5, 0.625, 0.5) + tilt * 0.375;
let cap = [
(-1.0, -1.0, 7.0, 6.0),
(-1.0, 1.0, 7.0, 8.0),
(1.0, 1.0, 9.0, 8.0),
(1.0, -1.0, 9.0, 6.0),
]
.map(|(dx, dz, du, dv)| {
let corner = center + Vec3::new(dx * 0.0625, 0.0, dz * 0.0625);
(corner, (u + du / 256.0, v + dv / 256.0))
});
builder.quad(cap, light, shade, WHITE);
for (a, b) in [
(Vec3::new(0.4375, 0.0, 0.0), Vec3::new(0.4375, 0.0, 1.0)),
(Vec3::new(0.5625, 0.0, 1.0), Vec3::new(0.5625, 0.0, 0.0)),
(Vec3::new(0.0, 0.0, 0.5625), Vec3::new(1.0, 0.0, 0.5625)),
(Vec3::new(1.0, 0.0, 0.4375), Vec3::new(0.0, 0.0, 0.4375)),
] {
let edge = 15.99 / 256.0;
builder.quad(
[
(a + offset + Vec3::Y, (u, v)),
(a + offset + tilt, (u, v + edge)),
(b + offset + tilt, (u + edge, v + edge)),
(b + offset + Vec3::Y, (u + edge, v)),
],
light,
shade,
WHITE,
);
}
}