diff --git a/CHANGELOG.md b/CHANGELOG.md index 23e46666..0a579c01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Increase world size - Fix issue where chunks were only serialized on the client - Add feature flags for debug rendering +- Add grass ## 0.1.1 diff --git a/Cargo.toml b/Cargo.toml index 4dc1e03b..bd618c45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,4 +61,5 @@ ortho_camera = [] lock_player = [] physics_debug = [] raycast_debug = [] +skip_terrain = [] visual_debug = ["wireframe", "physics_debug", "raycast_debug"] diff --git a/assets/textures/texture_atlas.png b/assets/textures/texture_atlas.png index b63670ad..0cd48675 100644 Binary files a/assets/textures/texture_atlas.png and b/assets/textures/texture_atlas.png differ diff --git a/src/client/collider/systems.rs b/src/client/collider/systems.rs index 8b7c2d0c..528d17b4 100644 --- a/src/client/collider/systems.rs +++ b/src/client/collider/systems.rs @@ -1,3 +1,5 @@ +use terrain_util::client_block::block_properties; + use crate::prelude::*; static COLLIDER_GRID_SIZE: u32 = 4; @@ -56,7 +58,7 @@ pub fn handle_collider_update_events_system( match block { Some(block) => { - if block != BlockId::Air { + if block_properties(block).has_collider { transform.translation = collider_position + COLLIDER_CUBOID_WIDTH / 2.0; } else { transform.translation = COLLIDER_RESTING_POSITION; @@ -133,7 +135,7 @@ mod tests { Vec3::new(1.0, 1.0, 1.0), ); resource.insert_chunks(chunks); - resource.set_block( + resource.update_block( Vec3 { x: 6.0, y: 7.0, diff --git a/src/client/player/systems/controller.rs b/src/client/player/systems/controller.rs index e0a34bf4..a37b94e6 100644 --- a/src/client/player/systems/controller.rs +++ b/src/client/player/systems/controller.rs @@ -1,8 +1,12 @@ use crate::prelude::*; -#[cfg(not(feature = "lock_player"))] +#[cfg(feature = "skip_terrain")] +const SPAWN_POINT: Vec3 = Vec3::new(0.0, 1.0, 0.0); + +#[cfg(all(not(feature = "skip_terrain"), not(feature = "lock_player")))] const SPAWN_POINT: Vec3 = Vec3::new(0.0, 64.0, 0.0); -#[cfg(feature = "lock_player")] + +#[cfg(all(not(feature = "skip_terrain"), feature = "lock_player"))] const SPAWN_POINT: Vec3 = Vec3::new(128.0, 96.0, -128.0); pub fn setup_player_camera(mut commands: Commands) { diff --git a/src/client/player/systems/terrain.rs b/src/client/player/systems/terrain.rs index 398c87e5..33346b1a 100644 --- a/src/client/player/systems/terrain.rs +++ b/src/client/player/systems/terrain.rs @@ -8,7 +8,7 @@ pub fn handle_block_update_events( mut client: ResMut, ) { for event in block_update_events.read() { - chunk_manager.set_block(event.position, event.block); + chunk_manager.update_block(event.position, event.block); info!("Block update message: {:?}", event.position); chunk_mesh_update_events.send(terrain_events::ChunkMeshUpdateEvent { diff --git a/src/client/terrain/mod.rs b/src/client/terrain/mod.rs index 04588a01..44db79a6 100644 --- a/src/client/terrain/mod.rs +++ b/src/client/terrain/mod.rs @@ -12,14 +12,30 @@ impl Plugin for TerrainPlugin { fn build(&self, app: &mut App) { info!("Building TerrainPlugin"); app.insert_resource(ChunkManager::new()); - app.insert_resource(terrain_resources::SpawnAreaLoaded(false)); app.insert_resource(util::TextureManager::new()); + app.insert_resource(resources::RenderMaterials::new()); app.add_event::(); app.add_event::(); app.add_event::(); - app.add_systems(Startup, terrain_systems::prepare_spawn_area_system); - app.add_systems(Startup, terrain_systems::generate_world_system); - app.add_systems(Update, terrain_systems::handle_chunk_mesh_update_events); - app.add_systems(Update, terrain_systems::handle_terrain_regeneration_events); + app.add_systems(Startup, terrain_systems::prepare_mesher_materials_system); + #[cfg(feature = "skip_terrain")] + { + app.insert_resource(terrain_resources::SpawnAreaLoaded(true)); + app.add_systems(Startup, terrain_systems::generate_simple_ground_system); + } + #[cfg(not(feature = "skip_terrain"))] + { + app.insert_resource(terrain_resources::SpawnAreaLoaded(false)); + app.add_systems(Startup, terrain_systems::prepare_spawn_area_system); + app.add_systems(Startup, terrain_systems::generate_world_system); + app.add_systems( + Update, + terrain_systems::handle_chunk_mesh_update_events_system, + ); + app.add_systems( + Update, + terrain_systems::handle_terrain_regeneration_events_system, + ); + } } } diff --git a/src/client/terrain/resources.rs b/src/client/terrain/resources.rs index 4efbe708..6b78ba78 100644 --- a/src/client/terrain/resources.rs +++ b/src/client/terrain/resources.rs @@ -8,3 +8,24 @@ impl SpawnAreaLoaded { resource.0 } } + +#[derive(Resource)] +pub struct RenderMaterials { + pub transparent_material: Option>, + pub chunk_material: Option>, +} + +impl Default for RenderMaterials { + fn default() -> Self { + Self::new() + } +} + +impl RenderMaterials { + pub fn new() -> RenderMaterials { + RenderMaterials { + transparent_material: None, + chunk_material: None, + } + } +} diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index 0c94f998..fbcd04c6 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -1,5 +1,36 @@ +use terrain_resources::RenderMaterials; +use terrain_util::create_cross_mesh_for_chunk; + use crate::prelude::*; +pub fn prepare_mesher_materials_system( + mut render_materials: ResMut, + mut materials: ResMut>, + asset_server: Res, +) { + let texture_handle = obtain_texture_handle(&asset_server); + + let material = create_transparent_material(texture_handle.clone()); + render_materials.transparent_material = Some(materials.add(material)); + + let material = create_chunk_material(texture_handle); + render_materials.chunk_material = Some(materials.add(material)); +} + +pub fn generate_simple_ground_system( + mut meshes: ResMut>, + mut materials: ResMut>, + mut commands: Commands, +) { + let mesh = Cuboid::new(64.0, 1.0, 64.0); + + commands.spawn(( + Mesh3d(meshes.add(mesh)), + MeshMaterial3d(materials.add(Color::srgba(1.0, 0.0, 1.0, 1.0))), + Name::new("Simple Ground Plane"), + )); +} + pub fn prepare_spawn_area_system(mut client: ResMut) { info!("Sending chunk requests for spawn area"); @@ -38,16 +69,14 @@ pub fn generate_world_system( }); } -#[allow(clippy::too_many_arguments)] -pub fn handle_chunk_mesh_update_events( +pub fn handle_chunk_mesh_update_events_system( mut commands: Commands, - asset_server: Res, mut meshes: ResMut>, - mut materials: ResMut>, chunk_manager: ResMut, mut chunk_mesh_update_events: EventReader, mut mesh_query: Query<(Entity, &terrain_components::ChunkMesh)>, texture_manager: ResMut, + materials: Res, ) { for event in chunk_mesh_update_events.read() { info!( @@ -64,11 +93,17 @@ pub fn handle_chunk_mesh_update_events( } add_chunk_objects( &mut commands, - &asset_server, &mut meshes, - &mut materials, chunk, &texture_manager, + &materials, + ); + add_cross_objects( + &mut commands, + chunk, + &materials, + &texture_manager, + &mut meshes, ); } None => { @@ -80,90 +115,113 @@ pub fn handle_chunk_mesh_update_events( fn add_chunk_objects( commands: &mut Commands, - asset_server: &Res, meshes: &mut ResMut>, - materials: &mut ResMut>, chunk: &Chunk, texture_manager: &terrain_util::TextureManager, + materials: &RenderMaterials, ) { - if let Some(mesh) = create_chunk_mesh(chunk, texture_manager) { - let texture_handle = obtain_texture_handle(asset_server).clone(); - let material = create_chunk_material(texture_handle, &mut ResMut::reborrow(materials)); - spawn_chunk( - commands, - &mut ResMut::reborrow(meshes), - material, - mesh, - chunk, - ); + if let Some(mesh) = terrain_util::create_chunk_mesh(chunk, texture_manager) { + let material = materials + .chunk_material + .clone() + .expect("Chunk material is loaded"); + + let meshes: &mut Mut> = &mut ResMut::reborrow(meshes); + commands.spawn(( + Mesh3d(meshes.add(mesh)), + Transform::from_xyz( + chunk.position.x * CHUNK_SIZE as f32, + chunk.position.y * CHUNK_SIZE as f32, + chunk.position.z * CHUNK_SIZE as f32, + ), + MeshMaterial3d(material), + player_components::Raycastable, + terrain_components::ChunkMesh { + key: [ + chunk.position.x as i32, + chunk.position.y as i32, + chunk.position.z as i32, + ], + }, + Name::from("Transparent Chunk Mesh"), + )); } } -fn create_chunk_mesh( +fn add_cross_objects( + commands: &mut Commands, chunk: &Chunk, + materials: &RenderMaterials, texture_manager: &terrain_util::TextureManager, -) -> Option { - terrain_util::create_chunk_mesh(chunk, texture_manager) + meshes: &mut ResMut>, +) { + if let Some(mesh) = create_cross_mesh_for_chunk(chunk, texture_manager) { + let mesh_handle = meshes.add(mesh); + + commands.spawn(( + Mesh3d(mesh_handle), + MeshMaterial3d( + materials + .transparent_material + .clone() + .expect("Transparent material exists"), + ), + Transform::from_xyz( + chunk.position.x * CHUNK_SIZE as f32, + chunk.position.y * CHUNK_SIZE as f32, + chunk.position.z * CHUNK_SIZE as f32, + ), + terrain_components::ChunkMesh { + key: [ + chunk.position.x as i32, + chunk.position.y as i32, + chunk.position.z as i32, + ], + }, + )); + } +} + +fn create_transparent_material(texture_handle: Handle) -> StandardMaterial { + StandardMaterial { + perceptual_roughness: 1.0, + double_sided: true, + cull_mode: None, + reflectance: 0.0, + unlit: false, + specular_transmission: 0.0, + alpha_mode: AlphaMode::Mask(1.0), + base_color_texture: Some(texture_handle), + ..default() + } } #[cfg(not(feature = "wireframe"))] -fn create_chunk_material( - texture_handle: Handle, - materials: &mut Mut>, -) -> Handle { - materials.add(StandardMaterial { +fn create_chunk_material(texture_handle: Handle) -> StandardMaterial { + StandardMaterial { perceptual_roughness: 0.5, reflectance: 0.0, unlit: false, specular_transmission: 0.0, base_color_texture: Some(texture_handle), ..default() - }) + } } #[cfg(feature = "wireframe")] -fn create_chunk_material( - _texture_handle: Handle, - materials: &mut Mut>, -) -> Handle { - materials.add(StandardMaterial { +fn create_chunk_material(_texture_handle: Handle) -> StandardMaterial { + StandardMaterial { base_color: Color::srgba(0.0, 0.0, 0.0, 0.0), alpha_mode: AlphaMode::Mask(0.5), ..default() - }) + } } fn obtain_texture_handle(asset_server: &Res) -> Handle { asset_server.load("textures/texture_atlas.png") } -fn spawn_chunk( - commands: &mut Commands, - meshes: &mut Mut>, - material: Handle, - mesh: Mesh, - chunk: &Chunk, -) { - commands.spawn(( - Mesh3d(meshes.add(mesh)), - Transform::from_xyz( - chunk.position.x * CHUNK_SIZE as f32, - chunk.position.y * CHUNK_SIZE as f32, - chunk.position.z * CHUNK_SIZE as f32, - ), - MeshMaterial3d(material), - player_components::Raycastable, - terrain_components::ChunkMesh { - key: [ - chunk.position.x as i32, - chunk.position.y as i32, - chunk.position.z as i32, - ], - }, - )); -} - -pub fn handle_terrain_regeneration_events( +pub fn handle_terrain_regeneration_events_system( mut client: ResMut, mut world_regenerate_events: EventReader, chunk_manager: ResMut, diff --git a/src/client/terrain/util/blocks.rs b/src/client/terrain/util/blocks.rs index 65699f22..2c5b5742 100644 --- a/src/client/terrain/util/blocks.rs +++ b/src/client/terrain/util/blocks.rs @@ -15,8 +15,76 @@ pub enum TextureName { OakLeaves, OakLogTop, OakLogSide, + Tallgrass, } +pub mod client_block { + use super::TextureName; + use rsmc::BlockId; + + #[derive(Eq, Hash, PartialEq, Clone)] + pub enum MeshRepresentation { + None, + Cube([TextureName; 6]), + Cross([TextureName; 2]), + } + + use MeshRepresentation::*; + + pub struct BlockProperties { + pub has_collider: bool, + pub mesh_representation: MeshRepresentation, + } + + pub fn block_properties(block_id: BlockId) -> BlockProperties { + use TextureName::*; + + let touple = match block_id { + BlockId::Air => (false, None), + BlockId::Grass => ( + true, + Cube([GrassTop, Dirt, GrassSide, GrassSide, GrassSide, GrassSide]), + ), + BlockId::Dirt => (true, Cube([Dirt; 6])), + BlockId::Stone => (true, Cube([Stone; 6])), + BlockId::CobbleStone => (true, Cube([CobbleStone; 6])), + BlockId::Bedrock => (true, Cube([Bedrock; 6])), + BlockId::IronOre => (true, Cube([IronOre; 6])), + BlockId::CoalOre => (true, Cube([CoalOre; 6])), + BlockId::OakLeaves => (true, Cube([OakLeaves; 6])), + BlockId::OakLog => ( + true, + Cube([ + OakLogTop, OakLogTop, OakLogSide, OakLogSide, OakLogSide, OakLogSide, + ]), + ), + BlockId::Tallgrass => (false, Cross([Tallgrass, Tallgrass])), + }; + + BlockProperties { + has_collider: touple.0, + mesh_representation: touple.1, + } + } + + pub fn collect_all_texture_names() -> Vec { + BlockId::values() + .iter() + .flat_map(|block_id| { + let properties = block_properties(*block_id); + let mesh: MeshRepresentation = properties.mesh_representation; + + match mesh { + MeshRepresentation::None => vec![], + MeshRepresentation::Cube(textures) => Vec::from(textures), + MeshRepresentation::Cross(textures) => Vec::from(textures), + } + }) + .collect() + } +} + +use client_block::block_properties; use TextureName::*; #[derive(Resource)] @@ -55,7 +123,7 @@ impl TextureManager { [Stone, CobbleStone, GrassTop, OakLeaves], [IronOre, Sand, GrassSide, OakLogTop], [CoalOre, Bedrock, Dirt, OakLogSide], - [Air, Air, Air, Air], + [Tallgrass, Air, Air, Air], ]; let mut texture_positions = Vec::new(); @@ -83,51 +151,6 @@ pub struct Block { pub is_solid: bool, } -macro_rules! add_block { - ($block_id:expr, $texture_names:expr, $is_solid:expr) => { - Block { - id: $block_id, - texture_names: $texture_names, - is_solid: $is_solid, - } - }; -} - -pub static BLOCKS: [Block; 10] = [ - add_block!(BlockId::Air, [TextureName::Air; 6], false), - add_block!( - BlockId::Grass, - [ - TextureName::GrassTop, - TextureName::Dirt, - TextureName::GrassSide, - TextureName::GrassSide, - TextureName::GrassSide, - TextureName::GrassSide, - ], - true - ), - add_block!(BlockId::Dirt, [TextureName::Dirt; 6], true), - add_block!(BlockId::Stone, [TextureName::Stone; 6], true), - add_block!(BlockId::CobbleStone, [TextureName::CobbleStone; 6], true), - add_block!(BlockId::Bedrock, [TextureName::Bedrock; 6], true), - add_block!(BlockId::IronOre, [TextureName::IronOre; 6], true), - add_block!(BlockId::CoalOre, [TextureName::CoalOre; 6], true), - add_block!(BlockId::OakLeaves, [TextureName::OakLeaves; 6], true), - add_block!( - BlockId::OakLog, - [ - TextureName::OakLogTop, - TextureName::OakLogTop, - TextureName::OakLogSide, - TextureName::OakLogSide, - TextureName::OakLogSide, - TextureName::OakLogSide, - ], - true - ), -]; - type TextureUV = [f32; 2]; impl Block { @@ -136,8 +159,18 @@ impl Block { face: CubeFace, texture_manager: &TextureManager, ) -> Option<[f32; 2]> { - let block = &BLOCKS[block_id as usize]; - let texture_name = block.texture_names[face as usize]; - texture_manager.get_texture_uv(texture_name).copied() + let properties = block_properties(block_id); + let mesh = properties.mesh_representation; + + let texture_option: Option = match mesh { + client_block::MeshRepresentation::None => None, + client_block::MeshRepresentation::Cube(textures) => Some(textures[face as usize]), + client_block::MeshRepresentation::Cross(textures) => Some(textures[face as usize]), + }; + + match texture_option { + Some(texture_name) => texture_manager.get_texture_uv(texture_name).copied(), + None => None, + } } } diff --git a/src/client/terrain/util/cross_mesher.rs b/src/client/terrain/util/cross_mesher.rs new file mode 100644 index 00000000..b3954665 --- /dev/null +++ b/src/client/terrain/util/cross_mesher.rs @@ -0,0 +1,105 @@ +use terrain_util::{ + client_block::{block_properties, MeshRepresentation}, + create_cube_mesh_from_data, GeometryData, TextureManager, Vertex, +}; + +use crate::prelude::*; + +pub fn create_cross_mesh_for_chunk( + chunk: &Chunk, + texture_manager: &TextureManager, +) -> Option { + let geometry_data = create_cross_geometry_for_chunk(chunk, texture_manager); + + create_cube_mesh_from_data(geometry_data) +} + +fn create_cross_geometry_for_chunk( + chunk: &Chunk, + texture_manager: &TextureManager, +) -> GeometryData { + let mut position = vec![]; + let mut uv = vec![]; + let mut normal = vec![]; + let mut indices = vec![]; + + let mut index_offset = 0; + + for x in 0..CHUNK_SIZE { + for y in 0..CHUNK_SIZE { + for z in 0..CHUNK_SIZE { + let block_id = chunk.get(x, y, z); + let pos = Vec3::new(x as f32, y as f32, z as f32); + let mesh_repr = block_properties(block_id).mesh_representation; + + if let MeshRepresentation::Cross(textures) = mesh_repr { + CrossFace::values().iter().for_each(|cross_face| { + let face_verticies = cross_face_vertices(*cross_face); + + let face_uv = texture_manager + .get_texture_uv(textures[0]) + .expect("Texture is not present in manager"); + + for vertex in face_verticies { + position.push([ + pos.x + vertex.position[0] * 0.5 + 0.5, + pos.y + vertex.position[1] * 0.5 + 0.5, + pos.z + vertex.position[2] * 0.5 + 0.5, + ]); + + uv.push([ + face_uv[0] + vertex.uv[0] * 0.25, + face_uv[1] + vertex.uv[1] * 0.25, + ]); + normal.push(vertex.normal); + } + + let offsets = [0, 1, 3, 1, 2, 3]; + offsets.iter().for_each(|offset| { + indices.push(index_offset + offset); + }); + + index_offset += 4; + }); + } + } + } + } + + GeometryData { + position, + uv, + normal, + indices, + } +} + +#[derive(Debug, Clone, Copy)] +pub enum CrossFace { + Face1, + Face2, +} + +impl CrossFace { + pub fn values() -> [CrossFace; 2] { + [CrossFace::Face1, CrossFace::Face2] + } +} + +#[rustfmt::skip] +fn cross_face_vertices(face: CrossFace) -> [Vertex; 4] { + match face { + CrossFace::Face1 => [ + Vertex{ position: [-1.0, 1.0, -1.0], normal: [FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2], uv: [0.0, 0.0] }, + Vertex{ position: [ 1.0, 1.0, 1.0], normal: [FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2], uv: [1.0, 0.0] }, + Vertex{ position: [ 1.0, -1.0, 1.0], normal: [FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2], uv: [1.0, 1.0] }, + Vertex{ position: [-1.0, -1.0, -1.0], normal: [FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2], uv: [0.0, 1.0] }, + ], + CrossFace::Face2 => [ + Vertex{ position: [-1.0, 1.0, 1.0], normal: [-FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2], uv: [0.0, 0.0] }, + Vertex{ position: [ 1.0, 1.0, -1.0], normal: [-FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2], uv: [1.0, 0.0] }, + Vertex{ position: [ 1.0, -1.0, -1.0], normal: [-FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2], uv: [1.0, 1.0] }, + Vertex{ position: [-1.0, -1.0, 1.0], normal: [-FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2], uv: [0.0, 1.0] }, + ], + } +} diff --git a/src/client/terrain/util/cube_mesher.rs b/src/client/terrain/util/cube_mesher.rs new file mode 100644 index 00000000..45462e5c --- /dev/null +++ b/src/client/terrain/util/cube_mesher.rs @@ -0,0 +1,221 @@ +use terrain_util::{ + client_block::{block_properties, MeshRepresentation}, + create_cube_mesh_from_data, GeometryData, TextureManager, Vertex, +}; + +use crate::prelude::*; + +pub fn create_cube_geometry_data( + x: f32, + y: f32, + z: f32, + faces: u8, + block_id: BlockId, + texture_manager: &TextureManager, +) -> GeometryData { + let mut position = Vec::new(); + let mut uv = Vec::new(); + let mut normal = Vec::new(); + let mut indices = Vec::new(); + let mut index_offset = 0; + + CUBE_FACES.iter().enumerate().for_each(|(i, face)| { + if faces & (1 << i) == 0 { + return; + } + + let face_vertices = face_vertices(*face); + for vertex in face_vertices.iter() { + position.push([ + vertex.position[0] * 0.5 + x + 0.5, + vertex.position[1] * 0.5 + y + 0.5, + vertex.position[2] * 0.5 + z + 0.5, + ]); + + let block_uvs = Block::get_block_face_uvs(block_id, *face, texture_manager).unwrap(); + uv.push([ + block_uvs[0] + vertex.uv[0] * 0.25, + block_uvs[1] + (1.0 - vertex.uv[1]) * 0.25, + ]); + normal.push(vertex.normal); + } + + let offsets = [0, 1, 2, 2, 1, 3]; + offsets.iter().for_each(|offset| { + indices.push(index_offset + offset); + }); + index_offset += 4; + }); + + GeometryData { + position, + uv, + normal, + indices, + } +} + +pub fn create_chunk_mesh(chunk: &Chunk, texture_manager: &TextureManager) -> Option { + let mut geometry_data = GeometryData { + position: Vec::new(), + uv: Vec::new(), + normal: Vec::new(), + indices: Vec::new(), + }; + + for x in 1..CHUNK_SIZE + 1 { + for y in 1..CHUNK_SIZE + 1 { + for z in 1..CHUNK_SIZE + 1 { + let block_id = chunk.get_unpadded(x, y, z); + + match block_properties(block_id).mesh_representation { + MeshRepresentation::Cube(_) => {} + _ => continue, + } + + fn update_mask( + chunk: &Chunk, + mask: &mut u8, + value: u8, + x: usize, + y: usize, + z: usize, + ) { + match block_properties(chunk.get_unpadded(x, y, z)).mesh_representation { + MeshRepresentation::Cube(_) => {} + _ => *mask |= value, + } + } + + let mut mask = 0b000000; + + update_mask(chunk, &mut mask, 0b000001, x, y + 1, z); + update_mask(chunk, &mut mask, 0b000010, x, y - 1, z); + + update_mask(chunk, &mut mask, 0b000100, x + 1, y, z); + update_mask(chunk, &mut mask, 0b001000, x - 1, y, z); + + update_mask(chunk, &mut mask, 0b010000, x, y, z - 1); + update_mask(chunk, &mut mask, 0b100000, x, y, z + 1); + + let cube_data = create_cube_geometry_data( + (x - 1) as f32, + (y - 1) as f32, + (z - 1) as f32, + mask, + block_id, + texture_manager, + ); + + geometry_data.indices.extend( + cube_data + .indices + .iter() + .map(|i| i + geometry_data.position.len() as u32), + ); + geometry_data.position.extend(cube_data.position); + geometry_data.uv.extend(cube_data.uv); + geometry_data.normal.extend(cube_data.normal); + } + } + } + + create_cube_mesh_from_data(geometry_data) +} + +#[derive(Debug, Clone, Copy)] +pub enum CubeFace { + Top, + Bottom, + Right, + Left, + Back, + Forward, +} + +const CUBE_FACES: [CubeFace; 6] = [ + CubeFace::Top, + CubeFace::Bottom, + CubeFace::Right, + CubeFace::Left, + CubeFace::Back, + CubeFace::Forward, +]; + +#[rustfmt::skip] +fn face_vertices(face_index: CubeFace) -> [Vertex; 4] { + match face_index { + CubeFace::Left => [ + Vertex{ position: [-1.0, -1.0, -1.0], normal: [-1.0, 0.0, 0.0], uv: [0.0, 0.0] }, + Vertex{ position: [-1.0, -1.0, 1.0], normal: [-1.0, 0.0, 0.0], uv: [1.0, 0.0] }, + Vertex{ position: [-1.0, 1.0, -1.0], normal: [-1.0, 0.0, 0.0], uv: [0.0, 1.0] }, + Vertex{ position: [-1.0, 1.0, 1.0], normal: [-1.0, 0.0, 0.0], uv: [1.0, 1.0] }, + ], + CubeFace::Right => [ + Vertex{ position: [1.0, -1.0, 1.0], normal: [1.0, 0.0, 0.0], uv: [0.0, 0.0] }, + Vertex{ position: [1.0, -1.0, -1.0], normal: [1.0, 0.0, 0.0], uv: [1.0, 0.0] }, + Vertex{ position: [1.0, 1.0, 1.0], normal: [1.0, 0.0, 0.0], uv: [0.0, 1.0] }, + Vertex{ position: [1.0, 1.0, -1.0], normal: [1.0, 0.0, 0.0], uv: [1.0, 1.0] }, + ], + CubeFace::Bottom => [ + Vertex{ position: [1.0, -1.0, 1.0], normal: [0.0, -1.0, 0.0], uv: [0.0, 0.0] }, + Vertex{ position: [-1.0, -1.0, 1.0], normal: [0.0, -1.0, 0.0], uv: [1.0, 0.0] }, + Vertex{ position: [1.0, -1.0, -1.0], normal: [0.0, -1.0, 0.0], uv: [0.0, 1.0] }, + Vertex{ position: [-1.0, -1.0, -1.0], normal: [0.0, -1.0, 0.0], uv: [1.0, 1.0] }, + ], + CubeFace::Top => [ + Vertex{ position: [1.0, 1.0, -1.0], normal: [0.0, 1.0, 0.0], uv: [0.0, 0.0] }, + Vertex{ position: [-1.0, 1.0, -1.0], normal: [0.0, 1.0, 0.0], uv: [1.0, 0.0] }, + Vertex{ position: [1.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0], uv: [0.0, 1.0] }, + Vertex{ position: [-1.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0], uv: [1.0, 1.0] }, + ], + CubeFace::Back => [ + Vertex{ position: [1.0, -1.0, -1.0], normal: [0.0, 0.0, -1.0], uv: [0.0, 0.0] }, + Vertex{ position: [-1.0, -1.0, -1.0], normal: [0.0, 0.0, -1.0], uv: [1.0, 0.0] }, + Vertex{ position: [1.0, 1.0, -1.0], normal: [0.0, 0.0, -1.0], uv: [0.0, 1.0] }, + Vertex{ position: [-1.0, 1.0, -1.0], normal: [0.0, 0.0, -1.0], uv: [1.0, 1.0] }, + ], + CubeFace::Forward => [ + Vertex{ position: [-1.0, -1.0, 1.0], normal: [0.0, 0.0, 1.0], uv: [0.0, 0.0] }, + Vertex{ position: [1.0, -1.0, 1.0], normal: [0.0, 0.0, 1.0], uv: [1.0, 0.0] }, + Vertex{ position: [-1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0], uv: [0.0, 1.0] }, + Vertex{ position: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0], uv: [1.0, 1.0] } + ], + } +} + +#[cfg(test)] +mod tests { + use super::*; + use terrain_util::TextureManager; + + #[test] + fn test_create_cube_mesh_from_data() { + let geometry_data = GeometryData { + position: vec![ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + ], + uv: vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], + normal: vec![[0.0, 0.0, 1.0]; 4], + indices: vec![0, 1, 2, 2, 3, 0], + }; + + let mesh = create_cube_mesh_from_data(geometry_data); + assert!(mesh.is_some()); + } + + #[test] + fn test_create_cube_geometry_data() { + let texture_manager = TextureManager::new(); + let geometry_data = + create_cube_geometry_data(0.0, 0.0, 0.0, 0b111111, BlockId::Stone, &texture_manager); + + assert_eq!(geometry_data.position.len(), 6 * 4); + assert_eq!(geometry_data.uv.len(), 6 * 4); + assert_eq!(geometry_data.normal.len(), 6 * 4); + assert_eq!(geometry_data.indices.len(), 6 * 6); + } +} diff --git a/src/client/terrain/util/mesher.rs b/src/client/terrain/util/mesher.rs index 6e76df68..a15198f5 100644 --- a/src/client/terrain/util/mesher.rs +++ b/src/client/terrain/util/mesher.rs @@ -1,5 +1,3 @@ -use terrain_util::TextureManager; - use crate::prelude::*; pub fn create_cube_mesh_from_data(geometry_data: GeometryData) -> Option { @@ -28,145 +26,10 @@ pub fn create_cube_mesh_from_data(geometry_data: GeometryData) -> Option { ) } -pub fn create_cube_geometry_data( - x: f32, - y: f32, - z: f32, - faces: u8, - block_id: BlockId, - texture_manager: &TextureManager, -) -> GeometryData { - let mut position = Vec::new(); - let mut uv = Vec::new(); - let mut normal = Vec::new(); - let mut indices = Vec::new(); - let mut index_offset = 0; - - CUBE_FACES.iter().enumerate().for_each(|(i, face)| { - if faces & (1 << i) == 0 { - return; - } - - let face_vertices = face_vertices(*face); - for vertex in face_vertices.iter() { - position.push([ - vertex.position[0] * 0.5 + x + 0.5, - vertex.position[1] * 0.5 + y + 0.5, - vertex.position[2] * 0.5 + z + 0.5, - ]); - - let block_uvs = Block::get_block_face_uvs(block_id, *face, texture_manager).unwrap(); - uv.push([ - block_uvs[0] + vertex.uv[0] * 0.25 - 0.001, - block_uvs[1] + (1.0 - vertex.uv[1]) * 0.25, - ]); - normal.push(vertex.normal); - } - - let offsets = [0, 1, 2, 2, 1, 3]; - offsets.iter().for_each(|offset| { - indices.push(index_offset + offset); - }); - index_offset += 4; - }); - - GeometryData { - position, - uv, - normal, - indices, - } -} - -pub fn create_chunk_mesh(chunk: &Chunk, texture_manager: &TextureManager) -> Option { - let mut geometry_data = GeometryData { - position: Vec::new(), - uv: Vec::new(), - normal: Vec::new(), - indices: Vec::new(), - }; - - for x in 1..CHUNK_SIZE + 1 { - for y in 1..CHUNK_SIZE + 1 { - for z in 1..CHUNK_SIZE + 1 { - let block_id = chunk.get_unpadded(x, y, z); - - if block_id == BlockId::Air { - continue; - } - - fn update_mask( - chunk: &Chunk, - mask: &mut u8, - value: u8, - x: usize, - y: usize, - z: usize, - ) { - if chunk.get_unpadded(x, y, z) == BlockId::Air { - *mask |= value; - } - } - - let mut mask = 0b000000; - - update_mask(chunk, &mut mask, 0b000001, x, y + 1, z); - update_mask(chunk, &mut mask, 0b000010, x, y - 1, z); - - update_mask(chunk, &mut mask, 0b000100, x + 1, y, z); - update_mask(chunk, &mut mask, 0b001000, x - 1, y, z); - - update_mask(chunk, &mut mask, 0b010000, x, y, z - 1); - update_mask(chunk, &mut mask, 0b100000, x, y, z + 1); - - let cube_data = create_cube_geometry_data( - (x - 1) as f32, - (y - 1) as f32, - (z - 1) as f32, - mask, - block_id, - texture_manager, - ); - - geometry_data.indices.extend( - cube_data - .indices - .iter() - .map(|i| i + geometry_data.position.len() as u32), - ); - geometry_data.position.extend(cube_data.position); - geometry_data.uv.extend(cube_data.uv); - geometry_data.normal.extend(cube_data.normal); - } - } - } - - create_cube_mesh_from_data(geometry_data) -} - -#[derive(Debug, Clone, Copy)] -pub enum CubeFace { - Top, - Bottom, - Right, - Left, - Back, - Forward, -} - -const CUBE_FACES: [CubeFace; 6] = [ - CubeFace::Top, - CubeFace::Bottom, - CubeFace::Right, - CubeFace::Left, - CubeFace::Back, - CubeFace::Forward, -]; - -struct Vertex { - position: [f32; 3], - uv: [f32; 2], - normal: [f32; 3], +pub struct Vertex { + pub position: [f32; 3], + pub uv: [f32; 2], + pub normal: [f32; 3], } pub struct GeometryData { @@ -175,98 +38,3 @@ pub struct GeometryData { pub normal: Vec<[f32; 3]>, pub indices: Vec, } - -#[rustfmt::skip] -fn face_vertices(face_index: CubeFace) -> [Vertex; 4] { - match face_index { - CubeFace::Left => [ - Vertex{ position: [-1.0, -1.0, -1.0], normal: [-1.0, 0.0, 0.0], uv: [0.0, 0.0] }, - Vertex{ position: [-1.0, -1.0, 1.0], normal: [-1.0, 0.0, 0.0], uv: [1.0, 0.0] }, - Vertex{ position: [-1.0, 1.0, -1.0], normal: [-1.0, 0.0, 0.0], uv: [0.0, 1.0] }, - Vertex{ position: [-1.0, 1.0, 1.0], normal: [-1.0, 0.0, 0.0], uv: [1.0, 1.0] }, - ], - CubeFace::Right => [ - Vertex{ position: [1.0, -1.0, 1.0], normal: [1.0, 0.0, 0.0], uv: [0.0, 0.0] }, - Vertex{ position: [1.0, -1.0, -1.0], normal: [1.0, 0.0, 0.0], uv: [1.0, 0.0] }, - Vertex{ position: [1.0, 1.0, 1.0], normal: [1.0, 0.0, 0.0], uv: [0.0, 1.0] }, - Vertex{ position: [1.0, 1.0, -1.0], normal: [1.0, 0.0, 0.0], uv: [1.0, 1.0] }, - ], - CubeFace::Bottom => [ - Vertex{ position: [1.0, -1.0, 1.0], normal: [0.0, -1.0, 0.0], uv: [0.0, 0.0] }, - Vertex{ position: [-1.0, -1.0, 1.0], normal: [0.0, -1.0, 0.0], uv: [1.0, 0.0] }, - Vertex{ position: [1.0, -1.0, -1.0], normal: [0.0, -1.0, 0.0], uv: [0.0, 1.0] }, - Vertex{ position: [-1.0, -1.0, -1.0], normal: [0.0, -1.0, 0.0], uv: [1.0, 1.0] }, - ], - CubeFace::Top => [ - Vertex{ position: [1.0, 1.0, -1.0], normal: [0.0, 1.0, 0.0], uv: [0.0, 0.0] }, - Vertex{ position: [-1.0, 1.0, -1.0], normal: [0.0, 1.0, 0.0], uv: [1.0, 0.0] }, - Vertex{ position: [1.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0], uv: [0.0, 1.0] }, - Vertex{ position: [-1.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0], uv: [1.0, 1.0] }, - ], - CubeFace::Back => [ - Vertex{ position: [1.0, -1.0, -1.0], normal: [0.0, 0.0, -1.0], uv: [0.0, 0.0] }, - Vertex{ position: [-1.0, -1.0, -1.0], normal: [0.0, 0.0, -1.0], uv: [1.0, 0.0] }, - Vertex{ position: [1.0, 1.0, -1.0], normal: [0.0, 0.0, -1.0], uv: [0.0, 1.0] }, - Vertex{ position: [-1.0, 1.0, -1.0], normal: [0.0, 0.0, -1.0], uv: [1.0, 1.0] }, - ], - CubeFace::Forward => [ - Vertex{ position: [-1.0, -1.0, 1.0], normal: [0.0, 0.0, 1.0], uv: [0.0, 0.0] }, - Vertex{ position: [1.0, -1.0, 1.0], normal: [0.0, 0.0, 1.0], uv: [1.0, 0.0] }, - Vertex{ position: [-1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0], uv: [0.0, 1.0] }, - Vertex{ position: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0], uv: [1.0, 1.0] } - ], - } -} - -#[cfg(test)] -mod tests { - use super::*; - use terrain_util::TextureManager; - - #[test] - fn test_create_cube_mesh_from_data() { - let geometry_data = GeometryData { - position: vec![ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [1.0, 1.0, 0.0], - [0.0, 1.0, 0.0], - ], - uv: vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], - normal: vec![[0.0, 0.0, 1.0]; 4], - indices: vec![0, 1, 2, 2, 3, 0], - }; - - let mesh = create_cube_mesh_from_data(geometry_data); - assert!(mesh.is_some()); - } - - #[test] - fn test_create_cube_geometry_data() { - let texture_manager = TextureManager::new(); - let geometry_data = - create_cube_geometry_data(0.0, 0.0, 0.0, 0b111111, BlockId::Stone, &texture_manager); - - assert_eq!(geometry_data.position.len(), 6 * 4); - assert_eq!(geometry_data.uv.len(), 6 * 4); - assert_eq!(geometry_data.normal.len(), 6 * 4); - assert_eq!(geometry_data.indices.len(), 6 * 6); - } - - #[test] - fn test_create_chunk_mesh() { - let texture_manager = TextureManager::new(); - let mut chunk = Chunk::new(Vec3::new(0.0, 0.0, 0.0)); - - for x in 1..CHUNK_SIZE + 1 { - for y in 1..CHUNK_SIZE + 1 { - for z in 1..CHUNK_SIZE + 1 { - chunk.set_unpadded(x, y, z, BlockId::Stone); - } - } - } - - let mesh = create_chunk_mesh(&chunk, &texture_manager); - assert!(mesh.is_some()); - } -} diff --git a/src/client/terrain/util/mod.rs b/src/client/terrain/util/mod.rs index 664bacd1..c40fafe2 100644 --- a/src/client/terrain/util/mod.rs +++ b/src/client/terrain/util/mod.rs @@ -1,5 +1,9 @@ pub mod blocks; +pub mod cross_mesher; +pub mod cube_mesher; pub mod mesher; pub use blocks::*; +pub use cross_mesher::*; +pub use cube_mesher::*; pub use mesher::*; diff --git a/src/server/terrain/resources.rs b/src/server/terrain/resources.rs index bd2df78f..c74f188a 100644 --- a/src/server/terrain/resources.rs +++ b/src/server/terrain/resources.rs @@ -49,6 +49,10 @@ pub struct HeightAdjustParams { pub noise: NoiseFunctionParams, } +pub struct GrassParams { + pub frequency: u32, +} + #[derive(Debug)] pub struct NoiseFunctionParams { pub octaves: u32, @@ -79,6 +83,7 @@ pub struct TerrainGeneratorParams { pub density: DensityParams, pub cave: CaveParams, pub tree: TreeParams, + pub grass: GrassParams, } impl Default for TerrainGeneratorParams { @@ -142,6 +147,7 @@ impl Default for TerrainGeneratorParams { min_bush_radius: 3, max_bush_radius: 5, }, + grass: GrassParams { frequency: 10 }, } } } diff --git a/src/server/terrain/systems.rs b/src/server/terrain/systems.rs index b4d78b03..27f015cb 100644 --- a/src/server/terrain/systems.rs +++ b/src/server/terrain/systems.rs @@ -308,12 +308,14 @@ mod visualizer { ui.group(|ui| { ui.vertical(|ui| { ui.label("Trees"); - add_slider_const!(ui, &mut generator.params.tree.spawn_attempts_per_chunk, 0..=1000, "spawn attempts"); add_slider_const!(ui, &mut generator.params.tree.min_stump_height, 0..=20, "min_stump_height"); add_slider_const!(ui, &mut generator.params.tree.max_stump_height, 0..=20, "max_stump_height"); add_slider_const!(ui, &mut generator.params.tree.min_bush_radius, 0..=10, "min_bush_radius"); add_slider_const!(ui, &mut generator.params.tree.max_bush_radius, 0..=10, "max_bush_radius"); + + ui.label("Grass"); + add_slider_const!(ui, &mut generator.params.grass.frequency, 0..=100, "frequnecy"); }); }); diff --git a/src/server/terrain/util/generator.rs b/src/server/terrain/util/generator.rs index 58f9fb7e..65b714ab 100644 --- a/src/server/terrain/util/generator.rs +++ b/src/server/terrain/util/generator.rs @@ -59,8 +59,7 @@ impl Generator { z: z as f32, }; - let block = self.decorate_block(chunk, pos); - chunk.set_unpadded(x, y, z, block); + self.decorate_block(chunk, pos); }); for _ in 0..self.params.tree.spawn_attempts_per_chunk { @@ -181,14 +180,24 @@ impl Generator { blocks } - fn decorate_block(&self, chunk: &Chunk, position: Vec3) -> BlockId { + fn decorate_block(&self, chunk: &mut Chunk, position: Vec3) { let x = position.x as usize; let y = position.y as usize; let z = position.z as usize; let block = chunk.get_unpadded(x, y, z); + if block == BlockId::Air { - return block; + if y > 0 + && Chunk::valid_unpadded(x, y - 1, z) + && chunk.get_unpadded(x, y - 1, z) == BlockId::Grass + { + let random_number = rand::random_range(0..=self.params.grass.frequency); + if random_number == 0 { + chunk.set_unpadded(x, y, z, BlockId::Tallgrass); + } + } + return; } let mut depth_below_nearest_air = 0; @@ -208,11 +217,13 @@ impl Generator { depth_below_nearest_air += 1; } - match depth_below_nearest_air { + let block = match depth_below_nearest_air { 0_i32..=1_i32 => BlockId::Grass, 2..3 => BlockId::Dirt, _ => BlockId::Stone, - } + }; + + chunk.set_unpadded(x, y, z, block); } fn generate_block(&self, position: Vec3) -> BlockId { diff --git a/src/shared/blocks.rs b/src/shared/blocks.rs index 457e18b3..300c8b95 100644 --- a/src/shared/blocks.rs +++ b/src/shared/blocks.rs @@ -1,39 +1,76 @@ +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)] +pub enum BlockId { + Air, + Grass, + Dirt, + Stone, + CobbleStone, + Bedrock, + IronOre, + CoalOre, + OakLeaves, + OakLog, + Tallgrass, +} + use serde::{Deserialize, Serialize}; +use BlockId::*; -macro_rules! enum_from_u8 { - ($name:ident { $( $variant:ident ),* $(,)? }) => { - #[repr(u8)] - #[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize)] - pub enum $name { - $( $variant ),* +impl From for BlockId { + fn from(value: u8) -> Self { + match value { + 0 => Air, + 1 => Grass, + 2 => Dirt, + 3 => Stone, + 4 => CobbleStone, + 5 => Bedrock, + 6 => IronOre, + 7 => CoalOre, + 8 => OakLeaves, + 9 => OakLog, + 10 => Tallgrass, + _ => panic!("Invalid block id"), } + } +} - impl $name { - pub fn from_u8(value: u8) -> Option<$name> { - match value { - $(x if x == $name::$variant as u8 => Some($name::$variant),)* - _ => None, - } - } - - pub fn to_u8(&self) -> u8 { - self.clone() as u8 - } +impl From for u8 { + fn from(val: BlockId) -> Self { + match val { + Air => 0, + Grass => 1, + Dirt => 2, + Stone => 3, + CobbleStone => 4, + Bedrock => 5, + IronOre => 6, + CoalOre => 7, + OakLeaves => 8, + OakLog => 9, + Tallgrass => 10, } - }; + } } -enum_from_u8! { - BlockId { - Air, - Grass, - Dirt, - Stone, - CobbleStone, - Bedrock, - IronOre, - CoalOre, - OakLeaves, - OakLog +impl BlockId { + pub fn values() -> [BlockId; 11] { + [ + Air, + Grass, + Dirt, + Stone, + CobbleStone, + Bedrock, + IronOre, + CoalOre, + OakLeaves, + OakLog, + Tallgrass, + ] + } + + pub fn supports_grass(&self) -> bool { + *self == Grass || *self == Dirt } } diff --git a/src/shared/chunk_serializer.rs b/src/shared/chunk_serializer.rs index ade60dfd..18294f20 100644 --- a/src/shared/chunk_serializer.rs +++ b/src/shared/chunk_serializer.rs @@ -12,7 +12,14 @@ impl Serialize for Chunk { where S: serde::Serializer, { - let data_as_u8: Vec = self.data.iter().map(|block_id| block_id.to_u8()).collect(); + let data_as_u8: Vec = self + .data + .iter() + .map(|block_id| { + let block_byte: u8 = (*block_id).into(); + block_byte + }) + .collect(); let serialized_data = serialize_buffer(data_as_u8); let mut state = serializer.serialize_struct("Chunk", 2)?; state.serialize_field("data", &serialized_data)?; @@ -50,7 +57,7 @@ impl<'de> Deserialize<'de> for Chunk { let deserialized_data = deserialize_buffer(bytes_slice); let data_as_block_id: [BlockId; CHUNK_LENGTH] = deserialized_data .into_iter() - .map(|i| BlockId::from_u8(i).unwrap()) + .map(BlockId::from) .collect::>() .try_into() .map_err(|_| serde::de::Error::custom("Failed to convert data to BlockId array"))?; diff --git a/src/shared/terrain.rs b/src/shared/terrain.rs index 5f532c56..ff3acd18 100644 --- a/src/shared/terrain.rs +++ b/src/shared/terrain.rs @@ -11,7 +11,6 @@ pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE * PADDED_C #[derive(Debug, Clone, Copy)] pub struct Chunk { - // #[serde(with = "BigArray")] pub data: [BlockId; CHUNK_LENGTH], pub position: Vec3, } @@ -25,7 +24,7 @@ impl Chunk { } pub fn valid_padded(x: usize, y: usize, z: usize) -> bool { - (1..CHUNK_SIZE).contains(&x) && (1..CHUNK_SIZE).contains(&y) && (1..CHUNK_SIZE).contains(&z) + (0..CHUNK_SIZE).contains(&x) && (0..CHUNK_SIZE).contains(&y) && (0..CHUNK_SIZE).contains(&z) } pub fn valid_unpadded(x: usize, y: usize, z: usize) -> bool { @@ -44,6 +43,17 @@ impl Chunk { self.set_unpadded(x + 1, y + 1, z + 1, value); } + pub fn update(&mut self, x: usize, y: usize, z: usize, value: BlockId) { + self.set(x, y, z, value); + + if !value.supports_grass() + && Self::valid_padded(x, y + 1, z) + && self.get(x, y + 1, z) == BlockId::Tallgrass + { + self.set(x, y + 1, z, BlockId::Air); + } + } + pub fn set_unpadded(&mut self, x: usize, y: usize, z: usize, value: BlockId) { self.data[Self::index(x, y, z)] = value; } @@ -155,7 +165,7 @@ impl ChunkManager { self.chunks.get_mut(&[x as i32, y as i32, z as i32]) } - pub fn set_block(&mut self, position: Vec3, block: BlockId) { + pub fn update_block(&mut self, position: Vec3, block: BlockId) { match self.chunk_from_selection(position) { Some(chunk) => { let chunk_position = Vec3::new( @@ -164,7 +174,7 @@ impl ChunkManager { chunk.position[2] * CHUNK_SIZE as f32, ); let local_position = (position - chunk_position).floor(); - chunk.set( + chunk.update( local_position.x as usize, local_position.y as usize, local_position.z as usize, @@ -280,7 +290,7 @@ mod tests { let block_position = Vec3::new(1.0, 1.0, 1.0); let block_id = BlockId::Stone; - chunk_manager.set_block(block_position, block_id); + chunk_manager.update_block(block_position, block_id); let retrieved_block = chunk_manager.get_block(block_position).unwrap(); assert_eq!(retrieved_block, block_id); } @@ -295,4 +305,29 @@ mod tests { let retrieved_chunk_positions = chunk_manager.get_all_chunk_positions(); assert_eq!(retrieved_chunk_positions.len(), 3); } + + #[test] + #[rustfmt::skip] + fn test_tallgrass_update() { + let mut chunk_manager = ChunkManager::new(); + let chunk_position = Vec3::new(0.0, 0.0, 0.0); + let chunk = Chunk::new(chunk_position); + chunk_manager.set_chunk(chunk_position, chunk); + + let grass_position = Vec3::new(0.0, 0.0, 0.0); + let tallgrass_position = Vec3::new(0.0, 1.0, 0.0); + + chunk_manager.update_block(grass_position, BlockId::Grass); + assert_eq!(chunk_manager.get_block(grass_position).unwrap(), BlockId::Grass); + chunk_manager.update_block(tallgrass_position, BlockId::Tallgrass); + assert_eq!(chunk_manager.get_block(tallgrass_position).unwrap(), BlockId::Tallgrass); + + chunk_manager.update_block(grass_position, BlockId::Dirt); + assert_eq!(chunk_manager.get_block(grass_position).unwrap(), BlockId::Dirt); + assert_eq!(chunk_manager.get_block(tallgrass_position).unwrap(), BlockId::Tallgrass); + + chunk_manager.update_block(grass_position, BlockId::Air); + assert_eq!(chunk_manager.get_block(grass_position).unwrap(), BlockId::Air); + assert_eq!(chunk_manager.get_block(tallgrass_position).unwrap(), BlockId::Air); + } }