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 / particles / ambient.rs

use common::math::floor_f64;
use common::world::Block;
use common::world::shape::BlockView;
use glam::DVec3;

use super::Particles;
use crate::world::ClientWorld;

impl Particles {
    pub fn ambient(&mut self, world: &mut ClientWorld, center: DVec3) {
        let center = center.to_array().map(floor_f64);
        for _ in 0..1000 {
            let [x, y, z] = center.map(|p| p + self.random.next_int(16) - self.random.next_int(16));
            match world.block(x, y, z) {
                Block::TORCH => {
                    let point = [x as f32 + 0.5, y as f32 + 0.7, z as f32 + 0.5].map(f64::from);
                    self.flame(DVec3::from_array(point) + torch_offset(world.metadata(x, y, z)));
                }
                Block::LIT_FURNACE => {
                    let mut point = [
                        x as f32 + 0.5,
                        y as f32 + self.random.next_float() * 6.0 / 16.0,
                        z as f32 + 0.5,
                    ];
                    let offset = self.random.next_float() * 0.6 - 0.3;
                    let (dx, dz) = match world.metadata(x, y, z) {
                        2 => (offset, -0.52),
                        3 => (offset, 0.52),
                        4 => (-0.52, offset),
                        5 => (0.52, offset),
                        _ => continue,
                    };
                    point[0] += dx;
                    point[2] += dz;
                    self.flame(DVec3::from_array(point.map(f64::from)));
                }
                Block::FIRE => self.fire(world, [x, y, z]),
                Block::REDSTONE_WIRE => self.wire(world.metadata(x, y, z), [x, y, z]),
                Block::REDSTONE_TORCH => self.redstone_torch(world.metadata(x, y, z), [x, y, z]),
                Block::POWERED_REPEATER => self.repeater(world.metadata(x, y, z), [x, y, z]),
                Block::PORTAL => self.portal(world, [x, y, z]),
                Block::LIT_REDSTONE_ORE => self.sparkle(world, [x, y, z]),
                Block::WATER | Block::FLOWING_WATER => {
                    // `BlockFluid.randomDisplayTick`
                    let metadata = world.metadata(x, y, z);
                    if self.random.next_int(64) == 0 && (1..8).contains(&metadata) {
                        world.sounds.push((
                            "liquid.water".into(),
                            DVec3::new(x as f64, y as f64, z as f64) + 0.5,
                            self.random.next_float() * 0.25 + 0.75,
                            self.random.next_float() + 0.5,
                        ));
                    }
                }
                Block::LAVA | Block::FLOWING_LAVA => {
                    let above = world.block(x, y + 1, z);
                    if above.is_air() && !above.is_opaque_cube() && self.random.next_int(100) == 0 {
                        let point = DVec3::new(
                            f64::from(x as f32 + self.random.next_float()),
                            f64::from(y) + 1.0,
                            f64::from(z as f32 + self.random.next_float()),
                        );
                        self.effect("lava", point, DVec3::ZERO);
                    }
                }
                _ => {}
            }
        }
    }

    /// The client half of `TileEntityMobSpawner.updateEntity`: smoke and flames in the cage
    /// of each spawner near a player.
    pub fn spawners(&mut self, world: &ClientWorld, player: DVec3) {
        let center = common::world::ChunkPos::containing(floor_f64(player.x), floor_f64(player.z));
        for [x, y, z] in world.spawners_near(center, 2) {
            let middle = DVec3::new(f64::from(x), f64::from(y), f64::from(z)) + 0.5;
            if middle.distance_squared(player) < 256.0 {
                let point = self.random_point([x, y, z]);
                self.flame(point);
            }
        }
    }

    /// A random point in the block at `(x, y, z)`.
    fn random_point(&mut self, position: [i32; 3]) -> DVec3 {
        DVec3::from_array(position.map(|v| f64::from(v as f32 + self.random.next_float())))
    }

    /// A point jittered by up to 0.1 around `(x, y, z)` offset by `base`, the way redstone
    /// torches and repeaters place their dust.
    fn jitter(&mut self, [x, y, z]: [i32; 3], base: f32) -> DVec3 {
        let mut jitter = |v: i32, offset: f32| {
            f64::from(v as f32 + offset) + f64::from(self.random.next_float() - 0.5) * 0.2
        };
        DVec3::new(jitter(x, 0.5), jitter(y, base), jitter(z, 0.5))
    }

    /// `BlockRedstoneWire.randomDisplayTick`: dust as red as the wire's power.
    fn wire(&mut self, power: u8, [x, y, z]: [i32; 3]) {
        if power == 0 {
            return;
        }
        let px = f64::from(x) + 0.5 + (f64::from(self.random.next_float()) - 0.5) * 0.2;
        let py = f64::from(y as f32 + 1.0 / 16.0);
        let pz = f64::from(z) + 0.5 + (f64::from(self.random.next_float()) - 0.5) * 0.2;
        let level = f32::from(power) / 15.0;
        let color = DVec3::new(
            f64::from(level * 0.6 + 0.4),
            f64::from((level * level * 0.7 - 0.5).max(0.0)),
            f64::from((level * level * 0.6 - 0.7).max(0.0)),
        );
        self.effect("reddust", DVec3::new(px, py, pz), color);
    }

    /// `BlockRedstoneTorch.randomDisplayTick` of a lit torch.
    fn redstone_torch(&mut self, metadata: u8, position: [i32; 3]) {
        let point = self.jitter(position, 0.7) + torch_offset(metadata);
        self.effect("reddust", point, DVec3::ZERO);
    }

    /// `BlockRedstoneRepeater.randomDisplayTick`: dust from either of its torches.
    fn repeater(&mut self, metadata: u8, position: [i32; 3]) {
        const TORCH: [f64; 4] = [-0.0625, 0.0625, 0.1875, 0.3125];
        let point = self.jitter(position, 0.4);
        let offset = if self.random.next_int(2) == 0 {
            0.3125
        } else {
            -TORCH[usize::from(metadata & 12) >> 2]
        };
        let shift = match metadata & 3 {
            0 => DVec3::new(0.0, 0.0, -offset),
            1 => DVec3::new(offset, 0.0, 0.0),
            2 => DVec3::new(0.0, 0.0, offset),
            _ => DVec3::new(-offset, 0.0, 0.0),
        };
        self.effect("reddust", point + shift, DVec3::ZERO);
    }

    /// `BlockPortal.randomDisplayTick`: its hum and particles drifting out of its surface.
    fn portal(&mut self, world: &mut ClientWorld, [x, y, z]: [i32; 3]) {
        if self.random.next_int(100) == 0 {
            world.sounds.push((
                "portal.portal".into(),
                DVec3::new(x as f64, y as f64, z as f64) + 0.5,
                1.0,
                self.random.next_float() * 0.4 + 0.8,
            ));
        }
        let along_x =
            world.block(x - 1, y, z) == Block::PORTAL || world.block(x + 1, y, z) == Block::PORTAL;
        for _ in 0..4 {
            let mut point = self.random_point([x, y, z]);
            let side = f64::from(self.random.next_int(2) * 2 - 1);
            let mut velocity = DVec3::from_array(std::array::from_fn(|_| {
                (f64::from(self.random.next_float()) - 0.5) * 0.5
            }));
            let push = f64::from(self.random.next_float() * 2.0 * side as f32);
            if along_x {
                point.z = f64::from(z) + 0.5 + 0.25 * side;
                velocity.z = push;
            } else {
                point.x = f64::from(x) + 0.5 + 0.25 * side;
                velocity.x = push;
            }
            self.effect("portal", point, velocity);
        }
    }

    /// `BlockRedstoneOre.sparkle`: dust on the faces of glowing ore that are open to the air.
    fn sparkle(&mut self, world: &mut ClientWorld, [x, y, z]: [i32; 3]) {
        const OFFSET: f64 = 1.0 / 16.0;
        for face in 0..6 {
            let mut point = [x, y, z].map(|v| f64::from(v as f32 + world.rand.next_float()));
            let open =
                |dx: i32, dy: i32, dz: i32| !world.block(x + dx, y + dy, z + dz).is_opaque_cube();
            match face {
                0 if open(0, 1, 0) => point[1] = f64::from(y + 1) + OFFSET,
                1 if open(0, -1, 0) => point[1] = f64::from(y) - OFFSET,
                2 if open(0, 0, 1) => point[2] = f64::from(z + 1) + OFFSET,
                3 if open(0, 0, -1) => point[2] = f64::from(z) - OFFSET,
                4 if open(1, 0, 0) => point[0] = f64::from(x + 1) + OFFSET,
                5 if open(-1, 0, 0) => point[0] = f64::from(x) - OFFSET,
                _ => {}
            }
            // The original compares the height against 0 instead of the block's bottom
            let [px, py, pz] = point;
            if px < f64::from(x)
                || px > f64::from(x + 1)
                || py < 0.0
                || py > f64::from(y + 1)
                || pz < f64::from(z)
                || pz > f64::from(z + 1)
            {
                self.effect("reddust", DVec3::from_array(point), DVec3::ZERO);
            }
        }
    }

    fn flame(&mut self, point: DVec3) {
        self.effect("smoke", point, DVec3::ZERO);
        self.effect("flame", point, DVec3::ZERO);
    }

    fn fire(&mut self, world: &mut ClientWorld, [x, y, z]: [i32; 3]) {
        if self.random.next_int(24) == 0 {
            world.sounds.push((
                "fire.fire".into(),
                DVec3::new(x as f64 + 0.5, y as f64 + 0.5, z as f64 + 0.5),
                1.0 + self.random.next_float(),
                self.random.next_float() * 0.7 + 0.3,
            ));
        }
        let below = world.block(x, y - 1, z);
        if below.is_normal_cube() || below.fire_rates().0 > 0 {
            for _ in 0..3 {
                let point = DVec3::new(
                    f64::from(x as f32 + self.random.next_float()),
                    f64::from(y as f32 + self.random.next_float() * 0.5 + 0.5),
                    f64::from(z as f32 + self.random.next_float()),
                );
                self.effect("largesmoke", point, DVec3::ZERO);
            }
        } else {
            for (axis, direction) in [(0, -1), (0, 1), (2, -1), (2, 1), (1, 1)] {
                let mut neighbor = [x, y, z];
                neighbor[axis] += direction;
                let [nx, ny, nz] = neighbor;
                if world.block(nx, ny, nz).fire_rates().0 == 0 {
                    continue;
                }
                for _ in 0..2 {
                    let mut point = [x, y, z].map(|p| p as f32 + self.random.next_float());
                    let fraction = point[axis] - [x, y, z][axis] as f32;
                    point[axis] = [x, y, z][axis] as f32
                        + if direction < 0 {
                            fraction * 0.1
                        } else {
                            1.0 - fraction * 0.1
                        };
                    self.effect(
                        "largesmoke",
                        DVec3::from_array(point.map(f64::from)),
                        DVec3::ZERO,
                    );
                }
            }
        }
    }
}

/// Where a torch on the given side of a block shows its flame, relative to a standing one.
fn torch_offset(metadata: u8) -> DVec3 {
    let (up, out) = (f64::from(0.22_f32), f64::from(0.27_f32));
    match metadata {
        1 => DVec3::new(-out, up, 0.0),
        2 => DVec3::new(out, up, 0.0),
        3 => DVec3::new(0.0, up, -out),
        4 => DVec3::new(0.0, up, out),
        _ => DVec3::ZERO,
    }
}