Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
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
Download file

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