rezcraft 0.2.0

Minecraft like game written in rust using wgpu, supporting both native and wasm
Documentation
use std::{
    mem::{self, MaybeUninit},
    num::NonZeroI32,
    sync::Arc,
};

use block_mesh::{
    greedy_quads, ndshape::ConstShape, ndshape::ConstShape3u32, GreedyQuadsBuffer, RIGHT_HANDED_Y_UP_CONFIG,
};
use cgmath::Vector3;
use either::Either;
use strum::IntoEnumIterator;
use wgpu::{
    util::{BufferInitDescriptor, DeviceExt},
    vertex_attr_array, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry,
    BindingType, Buffer, BufferAddress, BufferBindingType, BufferUsages, Device, IndexFormat, RenderPass, ShaderStages,
    VertexBufferLayout, VertexStepMode,
};

use super::coordinate_in_surrounding_buffers;
use crate::{
    engine::{
        face::{FaceDirection, SideDirection},
        resource::{Draw, Material, Vertex},
        TextureAtlas,
    },
    game::world::{Block, BlockBuffer, LightBuffer, LightVal, Voxel, CHUNK_SIZE_MESHING},
    misc::index::index_from_relative_pos_surrounding,
};

pub type ChunkShapeMeshing = ConstShape3u32<CHUNK_SIZE_MESHING, CHUNK_SIZE_MESHING, CHUNK_SIZE_MESHING>;

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Zeroable)]
pub struct BlockVertex {
    pub pos: [u8; 4],
    pub normal: [i8; 4],
    pub color: [u8; 4],
    pub texture_atlas_pos: [f32; 2],
    pub brightness: u8,
    pub transparency: u8,
}

unsafe impl bytemuck::Pod for BlockVertex {}

impl Vertex for BlockVertex {
    fn desc<'a>() -> VertexBufferLayout<'a> {
        use wgpu::VertexAttribute;

        static ATTRIBUTES: [VertexAttribute; 5] = vertex_attr_array![
            0 => Uint8x4,
            1 => Sint8x4,
            2 => Uint8x4,
            3 => Float32x2,
            4 => Uint8x2,
        ];

        VertexBufferLayout {
            array_stride: mem::size_of::<Self>() as BufferAddress,
            step_mode: VertexStepMode::Vertex,
            attributes: &ATTRIBUTES,
        }
    }
}

#[derive(Clone, Debug)]
pub struct ChunkMeshRaw {
    pub name: String,
    pub vertices: Vec<BlockVertex>,
    pub indices: Vec<u32>,
    pub chunk_pos: Vector3<i32>,
}

impl ChunkMeshRaw {
    pub fn new(
        name: String,
        vertices: Vec<BlockVertex>,
        indices: Vec<u32>,
        chunk_pos: Vector3<impl Into<i32>>,
    ) -> Self {
        Self {
            name,
            vertices,
            indices,
            chunk_pos: {
                let mut chunk_pos: Vector3<i32> =
                    Vector3::new(chunk_pos.x.into(), chunk_pos.y.into(), chunk_pos.z.into());

                if chunk_pos.x < 0 {
                    chunk_pos.x += 1
                }
                if chunk_pos.y < 0 {
                    chunk_pos.y += 1
                }
                if chunk_pos.z < 0 {
                    chunk_pos.z += 1
                }

                chunk_pos
            },
        }
    }
}

pub struct ChunkMesh {
    pub name: String,
    pub vertex_buffer: Buffer,
    pub index_buffer: Buffer,
    pub num_elements: u32,
    pub chunk_pos_buffer: Buffer,
    pub chunk_pos: BindGroup,
}

impl ChunkMesh {
    pub fn new(mesh_raw: ChunkMeshRaw, device: &Device) -> Self {
        let chunk_pos_buffer = device.create_buffer_init(&BufferInitDescriptor {
            label: None,
            contents: bytemuck::cast_slice(&[mesh_raw.chunk_pos.x, mesh_raw.chunk_pos.y, mesh_raw.chunk_pos.z, 0]),
            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
        });
        Self {
            name: mesh_raw.name,
            num_elements: mesh_raw.indices.len() as u32,
            vertex_buffer: device.create_buffer_init(&BufferInitDescriptor {
                label: None,
                contents: bytemuck::cast_slice(&mesh_raw.vertices),
                usage: BufferUsages::VERTEX,
            }),
            index_buffer: device.create_buffer_init(&BufferInitDescriptor {
                label: None,
                contents: bytemuck::cast_slice(&mesh_raw.indices),
                usage: BufferUsages::INDEX,
            }),
            chunk_pos: device.create_bind_group(&BindGroupDescriptor {
                label: None,
                layout: {
                    &device.create_bind_group_layout(&BindGroupLayoutDescriptor {
                        label: None,
                        entries: &[BindGroupLayoutEntry {
                            binding: 0,
                            visibility: ShaderStages::VERTEX,
                            ty: BindingType::Buffer {
                                ty: BufferBindingType::Uniform,
                                has_dynamic_offset: false,
                                min_binding_size: None,
                            },
                            count: None,
                        }],
                    })
                },
                entries: &[BindGroupEntry {
                    binding: 0,
                    resource: chunk_pos_buffer.as_entire_binding(),
                }],
            }),
            chunk_pos_buffer,
        }
    }
}

impl Draw for ChunkMesh {
    fn draw<'a>(
        &'a self,
        material: &'a Material,
        camera_bind_group: &'a BindGroup,
        settings_bind_group: &'a BindGroup,
        render_pass: &mut RenderPass<'a>,
    ) {
        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
        render_pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint32);
        render_pass.set_bind_group(0, &material.bind_group, &[]);
        render_pass.set_bind_group(1, camera_bind_group, &[]);
        render_pass.set_bind_group(2, settings_bind_group, &[]);
        render_pass.set_bind_group(3, &self.chunk_pos, &[]);
        render_pass.draw_indexed(0..self.num_elements, 0, 0..1);
    }
}

#[derive(Clone, Debug)]
pub struct MeshBuffer {
    pub solid_mesh: ChunkMeshRaw,
    pub transparent_mesh: ChunkMeshRaw,
}

impl MeshBuffer {
    pub fn new(
        chunk_pos: &Vector3<NonZeroI32>,
        surrounding_blocks: [Arc<BlockBuffer>; 7],
        surrounding_lights: [Arc<LightBuffer>; 7],
        texture_atlas: &TextureAtlas,
        transparency: bool,
        reused_buffers: &mut (GreedyQuadsBuffer, Vec<Voxel>),
    ) -> Self {
        let mesh = Self::generate_mesh(
            chunk_pos,
            surrounding_blocks,
            surrounding_lights,
            texture_atlas,
            transparency,
            reused_buffers,
        );

        Self {
            solid_mesh: mesh.0,
            transparent_mesh: mesh.1,
        }
    }

    const BUFFER_SIZE: usize = CHUNK_SIZE_MESHING.pow(3) as usize;
    fn generate_mesh(
        chunk_pos: &Vector3<NonZeroI32>,
        surrounding_blocks: [Arc<BlockBuffer>; 7],
        surrounding_lights: [Arc<LightBuffer>; 7],
        texture_atlas: &TextureAtlas,
        transparency: bool,
        reused_buffers: &mut (GreedyQuadsBuffer, Vec<Voxel>),
    ) -> (ChunkMeshRaw, ChunkMeshRaw) {
        let faces = RIGHT_HANDED_Y_UP_CONFIG.faces;

        if !surrounding_blocks[index_from_relative_pos_surrounding(&Vector3::new(0, 0, 0)) as usize]
            .contains_rendered_blocks()
        {
            return (
                ChunkMeshRaw::new(
                    format!("ChunkMesh - Solid {chunk_pos:?}"),
                    Vec::new(),
                    Vec::new(),
                    *chunk_pos,
                ),
                ChunkMeshRaw::new(
                    format!("ChunkMesh - Transparent {chunk_pos:?}"),
                    Vec::new(),
                    Vec::new(),
                    *chunk_pos,
                ),
            );
        }

        {
            for x in -1..CHUNK_SIZE_MESHING as i32 - 1 {
                for y in -1..CHUNK_SIZE_MESHING as i32 - 1 {
                    for z in -1..CHUNK_SIZE_MESHING as i32 - 1 {
                        let in_chunk_pos = Vector3::new(x, y, z);
                        reused_buffers.1
                            [ChunkShapeMeshing::linearize([(x + 1) as u32, (y + 1) as u32, (z + 1) as u32]) as usize] = {
                            let block = if let Some((chunk_pos, in_chunk_pos)) =
                                coordinate_in_surrounding_buffers(in_chunk_pos)
                            {
                                surrounding_blocks[index_from_relative_pos_surrounding(&chunk_pos) as usize]
                                    [&in_chunk_pos]
                                    .clone()
                            } else {
                                Block::default()
                            };

                            let face_lighting = {
                                if block.is_rendered() {
                                    Some(if let Some(light_source) = block.light_source() {
                                        [light_source.light_raw(); 6]
                                    } else {
                                        {
                                            let mut face_lighting: [MaybeUninit<_>; 6] =
                                                unsafe { MaybeUninit::uninit().assume_init() };

                                            FaceDirection::iter().for_each(|face| {
                                                face_lighting[face.as_index()] = MaybeUninit::new(
                                                    if let Some((chunk_pos, in_chunk_pos)) =
                                                        coordinate_in_surrounding_buffers(in_chunk_pos + face.as_dir())
                                                    {
                                                        surrounding_lights
                                                            [index_from_relative_pos_surrounding(&chunk_pos) as usize]
                                                            [&in_chunk_pos]
                                                            .light_raw()
                                                    } else {
                                                        LightVal::default().light_raw()
                                                    },
                                                )
                                            });

                                            unsafe { mem::transmute(face_lighting) }
                                        }
                                    })
                                } else {
                                    None
                                }
                            };

                            Voxel::new(&block, face_lighting)
                        }
                    }
                }
            }
        }

        reused_buffers.0.reset(MeshBuffer::BUFFER_SIZE);
        greedy_quads(
            &reused_buffers.1,
            &ChunkShapeMeshing {},
            [0; 3],
            [CHUNK_SIZE_MESHING - 1; 3],
            &faces,
            &mut reused_buffers.0,
        );

        let (num_indices, num_vertices) = (
            reused_buffers.0.quads.num_quads() * 6,
            reused_buffers.0.quads.num_quads() * 4,
        );
        let (mut solid_indices, mut solid_vertices) =
            (Vec::with_capacity(num_indices), Vec::with_capacity(num_vertices));
        let (mut transparent_indices, mut transparent_vertices) = (Vec::new(), Vec::new());

        for (group, face) in reused_buffers.0.quads.groups.iter().zip(faces.into_iter()) {
            for quad in group.iter() {
                solid_indices.extend_from_slice(&face.quad_mesh_indices(solid_vertices.len() as u32));
                transparent_indices.extend_from_slice(&face.quad_mesh_indices(transparent_vertices.len() as u32));

                let quad_mesh_poses = face.quad_mesh_positions(quad, 1.0);
                let normals = face.quad_mesh_normals();

                let pos = Vector3::new(quad.minimum[0] as i32, quad.minimum[1] as i32, quad.minimum[2] as i32);
                let voxel = reused_buffers.1
                    [ChunkShapeMeshing::linearize([(pos.x) as u32, (pos.y) as u32, (pos.z) as u32]) as usize]
                    .clone();

                for i in 0..4 {
                    let mesh_pos = quad_mesh_poses[i];
                    let normal = {
                        let normal = normals[i];
                        [normal[0] as i8, normal[1] as i8, normal[2] as i8, 0]
                    };

                    let face_direction =
                        FaceDirection::from_dir(&Vector3::new(normal[0] as i32, normal[1] as i32, normal[2] as i32))
                            .unwrap();

                    let light_color = voxel.face_lighting().unwrap()[face_direction.as_index()];

                    let texture_atlas_pos = {
                        if let Some(textures) = voxel.texture() {
                            let atlas_pos = texture_atlas.texture_coordinates(match textures {
                                Either::Left(texture) => texture,
                                Either::Right([texture_top, texture_side, texture_bottom]) => {
                                    match face_direction.into() {
                                        SideDirection::Top => texture_top,
                                        SideDirection::Side => texture_side,
                                        SideDirection::Bottom => texture_bottom,
                                    }
                                }
                            });
                            [atlas_pos.0, atlas_pos.1]
                        } else {
                            log::warn!("Creating vertex without texture");
                            [0.0, 0.0]
                        }
                    };

                    let pos = {
                        let temp_pos = [mesh_pos[0] as i32 - 1, mesh_pos[1] as i32 - 1, mesh_pos[2] as i32 - 1];
                        [temp_pos[0] as u8, temp_pos[1] as u8, temp_pos[2] as u8, 0]
                    };

                    if transparency && voxel.is_transparent() {
                        transparent_indices.extend_from_slice(&face.quad_mesh_indices(solid_vertices.len() as u32));
                        transparent_vertices.push(BlockVertex {
                            pos,
                            normal,
                            color: light_color,
                            texture_atlas_pos,
                            brightness: face_direction.brightness(),
                            transparency: 1,
                        })
                    } else {
                        solid_vertices.push(BlockVertex {
                            pos,
                            normal,
                            color: light_color,
                            texture_atlas_pos,
                            brightness: face_direction.brightness(),
                            transparency: 0,
                        })
                    }
                }
            }
        }

        (
            ChunkMeshRaw::new(
                format!("ChunkMesh - Solid {chunk_pos:?}"),
                solid_vertices,
                solid_indices,
                *chunk_pos,
            ),
            ChunkMeshRaw::new(
                format!("ChunkMesh - Transparent {chunk_pos:?}"),
                transparent_vertices,
                transparent_indices,
                *chunk_pos,
            ),
        )
    }
}