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 fluid.rs
//! `renderBlockFluids`.
use std::f64::consts::PI;
use common::math;
use common::world::{Block, Material};
use glam::{DVec3, Vec3};
use super::block::{Bounds, face_quad};
use super::{Builder, Cache, Light, Shade, WHITE, occludes};
/// Port of `BlockFluid.shouldSideBeRendered`.
fn fluid_face_visible(
cache: &Cache,
material: Material,
[x, y, z]: [i32; 3],
face: usize,
fancy: bool,
) -> bool {
let neighbor = cache.block(x, y, z);
let neighbor_material = neighbor.material();
if neighbor_material == material || neighbor_material == Material::Ice {
return false;
}
face == 1 || !occludes(neighbor, fancy)
}
/// Port of `BlockFluid.getBlockBrightness`: the brighter of the block and the one above.
fn fluid_light(cache: &Cache, x: i32, y: i32, z: i32) -> Light {
cache.light(x, y, z).max(cache.light(x, y + 1, z))
}
fn percent_air(metadata: u8) -> f32 {
let metadata = if metadata >= 8 { 0 } else { metadata };
f32::from(metadata + 1) / 9.0
}
/// Port of `RenderBlocks.getFluidHeight`.
fn fluid_height(cache: &Cache, x: i32, y: i32, z: i32, material: Material) -> f32 {
let mut weight = 0;
let mut air = 0.0_f32;
for i in 0..4 {
let (sx, sz) = (x - (i & 1), z - (i >> 1 & 1));
if cache.material(sx, y + 1, sz) == material {
return 1.0;
}
let sample = cache.material(sx, y, sz);
if sample != material {
if !sample.is_solid() {
air += 1.0;
weight += 1;
}
} else {
let metadata = cache.metadata(sx, y, sz);
if metadata >= 8 || metadata == 0 {
air += percent_air(metadata) * 10.0;
weight += 10;
}
air += percent_air(metadata);
weight += 1;
}
}
1.0 - air / weight as f32
}
/// Port of `BlockFluid.getFlowDirection` and `getFlowVector`. `None` means still.
fn flow_angle(cache: &Cache, [x, y, z]: [i32; 3], material: Material) -> Option<f32> {
let decay = |x: i32, y: i32, z: i32| match cache.metadata(x, y, z) {
_ if cache.material(x, y, z) != material => -1,
metadata if metadata >= 8 => 0,
metadata => i32::from(metadata),
};
let normalize = |v: DVec3| {
let length = f64::from(v.length() as f32);
if length < 1.0e-4 {
DVec3::ZERO
} else {
v / length
}
};
let own = decay(x, y, z);
let mut flow = DVec3::ZERO;
for (dx, dz) in [(-1, 0), (0, -1), (1, 0), (0, 1)] {
let (nx, nz) = (x + dx, z + dz);
let neighbor = decay(nx, y, nz);
let weight = if neighbor < 0 {
if cache.material(nx, y, nz).is_solid() {
continue;
}
let below = decay(nx, y - 1, nz);
if below < 0 {
continue;
}
below - (own - 8)
} else {
neighbor - own
};
flow += DVec3::new(f64::from(dx * weight), 0.0, f64::from(dz * weight));
}
if cache.metadata(x, y, z) >= 8 {
let solid = |x: i32, y: i32, z: i32| {
let m = cache.material(x, y, z);
m != material && m != Material::Ice && m.is_solid()
};
let walled = [(0, -1), (0, 1), (-1, 0), (1, 0)]
.into_iter()
.any(|(dx, dz)| solid(x + dx, y, z + dz) || solid(x + dx, y + 1, z + dz));
if walled {
flow = normalize(flow) + DVec3::new(0.0, -6.0, 0.0);
}
}
let flow = normalize(flow);
(flow.x != 0.0 || flow.z != 0.0).then(|| (flow.z.atan2(flow.x) - PI * 0.5) as f32)
}
/// Port of `renderBlockFluids`.
pub fn fluid(cache: &Cache, builder: &mut Builder, block: Block, position: [i32; 3], fancy: bool) {
let [x, y, z] = position;
let material = block.material();
let texture = block.properties().map_or(0, |p| p.texture);
let top = fluid_face_visible(cache, material, [x, y + 1, z], 1, fancy);
let bottom = fluid_face_visible(cache, material, [x, y - 1, z], 0, fancy);
let sides = [(0, -1), (0, 1), (-1, 0), (1, 0)]
.map(|(dx, dz)| fluid_face_visible(cache, material, [x + dx, y, z + dz], 2, fancy));
if !top && !bottom && !sides.contains(&true) {
return;
}
let h00 = fluid_height(cache, x, y, z, material);
let h01 = fluid_height(cache, x, y, z + 1, material);
let h11 = fluid_height(cache, x + 1, y, z + 1, material);
let h10 = fluid_height(cache, x + 1, y, z, material);
let p = Vec3::new;
if top {
let flow = flow_angle(cache, position, material);
let tile = if flow.is_some() { texture + 1 } else { texture };
let (tile_x, tile_y) = (i32::from(tile & 15) * 16, i32::from(tile & 0xF0));
let (center_u, center_v) = if flow.is_some() {
((tile_x + 16) as f32 / 256.0, (tile_y + 16) as f32 / 256.0)
} else {
(
((f64::from(tile_x) + 8.0) / 256.0) as f32,
((f64::from(tile_y) + 8.0) / 256.0) as f32,
)
};
let angle = flow.unwrap_or(0.0);
let s = math::sin(angle) * 8.0 / 256.0;
let c = math::cos(angle) * 8.0 / 256.0;
builder.quad(
[
(p(0.0, h00, 0.0), (center_u - c - s, center_v - c + s)),
(p(0.0, h01, 1.0), (center_u - c + s, center_v + c + s)),
(p(1.0, h11, 1.0), (center_u + c + s, center_v + c - s)),
(p(1.0, h10, 0.0), (center_u + c - s, center_v - c - s)),
],
fluid_light(cache, x, y, z),
Shade::Top,
WHITE,
);
}
if bottom {
builder.quad(
face_quad(0, &Bounds::UNIT, texture),
fluid_light(cache, x, y - 1, z),
Shade::Bottom,
WHITE,
);
}
let (tile_x, tile_y) = (
f32::from((texture + 1) & 15) * 16.0,
f32::from((texture + 1) & 0xF0),
);
for (side, visible) in sides.into_iter().enumerate() {
if !visible {
continue;
}
let (ha, hb, a, b, (nx, nz)) = match side {
0 => (h00, h10, (0.0, 0.0), (1.0, 0.0), (x, z - 1)),
1 => (h11, h01, (1.0, 1.0), (0.0, 1.0), (x, z + 1)),
2 => (h01, h00, (0.0, 1.0), (0.0, 0.0), (x - 1, z)),
_ => (h10, h11, (1.0, 0.0), (1.0, 1.0), (x + 1, z)),
};
let u0 = tile_x / 256.0;
let u1 = ((f64::from(tile_x) + 16.0 - 0.01) / 256.0) as f32;
let va = (tile_y + (1.0 - ha) * 16.0) / 256.0;
let vb = (tile_y + (1.0 - hb) * 16.0) / 256.0;
let v_bottom = ((f64::from(tile_y) + 16.0 - 0.01) / 256.0) as f32;
builder.quad(
[
(p(a.0, ha, a.1), (u0, va)),
(p(b.0, hb, b.1), (u1, vb)),
(p(b.0, 0.0, b.1), (u1, v_bottom)),
(p(a.0, 0.0, a.1), (u0, v_bottom)),
],
fluid_light(cache, nx, y, nz),
if side < 2 { Shade::ZSide } else { Shade::XSide },
WHITE,
);
}
}