diff --git a/src/add_break_blocks.rs b/src/add_break_blocks.rs new file mode 100644 index 0000000..e990d5c --- /dev/null +++ b/src/add_break_blocks.rs @@ -0,0 +1,136 @@ +use crate::{one_d_cords, three_d_cords, *}; +use bevy::prelude::*; +use bevy_meshem::prelude::*; + +const RAY_FORWARD_STEP: f32 = 0.1; +const REACH_DISTANCE: u8 = 4; + +#[derive(Event)] +pub struct BlockChange { + pub blocks: Vec<([i32; 2], usize)>, + pub change: VoxelChange, +} + +pub fn add_break_detector( + mut block_change_event_writer: EventWriter, + player_query: Query<(&CurrentChunk, &Transform), With>, + buttons: Res>, +) { + if let Ok((chunk, tran)) = player_query.get_single() { + if tran.translation.y > HEIGHT as f32 || tran.translation.y < 0.0 { + return; + } + let chunk = (*chunk).0; + let forward = tran.forward(); + let pos = tran.translation; + + if buttons.just_pressed(MouseButton::Left) { + println!("Heya"); + block_change_event_writer.send(BlockChange { + blocks: blocks_in_the_way(pos, forward, REACH_DISTANCE) + .iter() + .map(|(x, y, z)| (*x, one_d_cords(*y, CHUNK_DIMS))) + .collect(), + change: VoxelChange::Broken, + }); + } + if buttons.just_pressed(MouseButton::Right) { + block_change_event_writer.send(BlockChange { + change: VoxelChange::Added, + blocks: blocks_in_the_way(pos, forward, REACH_DISTANCE) + .iter() + .map(|&(x, y, z)| { + let tmp = one_d_cords(y, CHUNK_DIMS); + if let Some(block) = get_neighbor(tmp, z, CHUNK_DIMS) { + dbg!((x, block)); + (x, block) + } else { + match z { + Top => panic!( + "\nIn-Game Error: \nMaximum build limit has been reached" + ), + Bottom => { + panic!("\nIn-Game Error: \nCan't build lower than y = 0.") + } + Right => ([x[0] + 1, x[1]], tmp - WIDTH + 1), + Left => ([x[0] - 1, x[1]], tmp + WIDTH - 1), + Back => ([x[0], x[1] + 1], tmp - WIDTH * (LENGTH - 1)), + Forward => ([x[0], x[1] - 1], tmp + WIDTH * (LENGTH - 1)), + } + } + }) + .collect(), + }); + } + } +} + +fn blocks_in_the_way(pos: Vec3, forward: Vec3, distance: u8) -> Vec<([i32; 2], [usize; 3], Face)> { + let step = forward * RAY_FORWARD_STEP; + let mut point = pos; + let mut current_block = [ + point.x.floor() + 0.5, + point.y.floor() + 0.5, + point.z.floor() + 0.5, + ]; + let mut to_return: Vec<([i32; 2], [usize; 3], Face)> = vec![]; + + while point.distance(pos) < distance as f32 { + point += step; + let tmp = [ + point.x.floor() + 0.5, + point.y.floor() + 0.5, + point.z.floor() + 0.5, + ]; + if tmp != current_block { + current_block = tmp; + let face = { + let mut r: Face = Top; + let mut p = point - step; + let nano_step = step / 20.0; + for _ in 1..21 { + p += nano_step; + let tmp = [p.x.floor() + 0.5, p.y.floor() + 0.5, p.z.floor() + 0.5]; + if tmp == current_block { + r = closest_face(p); + break; + } + } + r + }; + let block_pos = position_to_chunk_position(point, CHUNK_DIMS); + to_return.push((block_pos.0, block_pos.1, face)); + } + } + to_return +} + +fn closest_face(p: Vec3) -> Face { + let mut min = f32::MAX; + let mut face = Top; + + if (p.x.floor() - p.x).abs() < min { + min = (p.x.floor() - p.x).abs(); + face = Left; + } + if (p.x.ceil() - p.x).abs() < min { + min = (p.x.ceil() - p.x).abs(); + face = Right; + } + if (p.z.floor() - p.z).abs() < min { + min = (p.z.floor() - p.z).abs(); + face = Forward; + } + if (p.z.ceil() - p.z).abs() < min { + min = (p.z.ceil() - p.z).abs(); + face = Back; + } + if (p.y.floor() - p.y).abs() < min { + min = (p.y.floor() - p.y).abs(); + face = Bottom; + } + if (p.y.ceil() - p.y).abs() < min { + face = Top; + } + return face; +} diff --git a/src/block_reg.rs b/src/block_reg.rs index ecd237d..ed35818 100644 --- a/src/block_reg.rs +++ b/src/block_reg.rs @@ -9,6 +9,8 @@ pub const DIRT: Block = 1; pub const GRASS: Block = 2; pub const STONE: Block = 3; +pub const VOXEL_DIMS: [f32; 3] = [1.0, 1.0, 1.0]; + #[derive(Resource, Clone)] pub struct BlockRegistry { grass_block: Mesh, @@ -20,7 +22,7 @@ impl Default for BlockRegistry { fn default() -> Self { BlockRegistry { grass_block: generate_voxel_mesh( - [1.0, 1.0, 1.0], + VOXEL_DIMS, [4, 4], [ (Top, [0, 0]), @@ -32,7 +34,7 @@ impl Default for BlockRegistry { ], ), dirt_block: generate_voxel_mesh( - [1.0, 1.0, 1.0], + VOXEL_DIMS, [4, 4], [ (Top, [2, 0]), @@ -44,7 +46,7 @@ impl Default for BlockRegistry { ], ), stone_block: generate_voxel_mesh( - [1.0, 1.0, 1.0], + VOXEL_DIMS, [4, 4], [ (Top, [3, 0]), @@ -71,7 +73,7 @@ impl VoxelRegistry for BlockRegistry { } fn get_voxel_dimensions(&self) -> [f32; 3] { - [1.0, 1.0, 1.0] + VOXEL_DIMS } fn get_center(&self) -> [f32; 3] { diff --git a/src/chunk.rs b/src/chunk.rs index 3a40d46..00d46e5 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,6 +1,6 @@ use crate::block_reg::{Block, AIR, DIRT, GRASS, STONE}; use bevy::prelude::Component; -use bevy_meshem::prelude::{Dimensions, MeshMD}; +use bevy_meshem::prelude::*; use noise::{NoiseFn, Perlin, Seedable}; const CHUNK_SIZE: usize = 16; pub const CHUNK_DIMS: Dimensions = (CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE); @@ -8,17 +8,26 @@ pub const HEIGHT: usize = CHUNK_DIMS.2; pub const WIDTH: usize = CHUNK_DIMS.0; pub const LENGTH: usize = CHUNK_DIMS.1; pub const CHUNK_LEN: usize = CHUNK_DIMS.0 * CHUNK_DIMS.1 * CHUNK_DIMS.2; -const NOISE_FACTOR_CONT: f64 = 0.015; +const NOISE_FACTOR_CONT: f64 = 0.020; // has to be greater than 1.0 -const NOISE_FACTOR_SCALE: f64 = 2.0; +const NOISE_FACTOR_SCALE: f64 = 1.8; #[derive(Component)] pub struct Chunk { pub meta_data: MeshMD, pub cords: [i32; 2], - pub compressed_chunk: Vec<(Block, usize)>, + // pub compressed_chunk: Vec<(Block, usize)>, + pub grid: [Block; CHUNK_LEN], } +#[derive(Component)] +pub struct BlockChangeQueue { + pub block_queue: Vec<([usize; 3], VoxelChange)>, +} + +#[derive(Component)] +pub struct ChunkCloseToPlayer; + pub fn generate_chunk(cords: [i32; 2], noise: &impl NoiseFn) -> [u16; CHUNK_LEN] { let mut height_map: [usize; CHUNK_DIMS.0 * CHUNK_DIMS.1] = [0; CHUNK_DIMS.0 * CHUNK_DIMS.1]; let mut chunk = [0; CHUNK_LEN]; @@ -38,7 +47,7 @@ pub fn generate_chunk(cords: [i32; 2], noise: &impl NoiseFn) -> [u16; CH for x in 0..CHUNK_DIMS.0 { if height_map[x + z * CHUNK_DIMS.0] < y { chunk[x + z * CHUNK_DIMS.0 + y * CHUNK_DIMS.0 * CHUNK_DIMS.1] = AIR; - } else if height_map[x + z * CHUNK_DIMS.0] == y { + } else if height_map[x + z * CHUNK_DIMS.0] == y && y > HEIGHT / 4 { chunk[x + z * CHUNK_DIMS.0 + y * CHUNK_DIMS.0 * CHUNK_DIMS.1] = GRASS; } else { chunk[x + z * CHUNK_DIMS.0 + y * CHUNK_DIMS.0 * CHUNK_DIMS.1] = DIRT; diff --git a/src/chunk_queue.rs b/src/chunk_queue.rs index cfbc2d4..3ce1c0b 100644 --- a/src/chunk_queue.rs +++ b/src/chunk_queue.rs @@ -45,6 +45,10 @@ impl ChunkMap { self.pos_to_ent.keys() } + pub fn iter(&self) -> bevy::utils::hashbrown::hash_map::Iter<'_, [i32; 2], Entity> { + self.pos_to_ent.iter() + } + pub fn change_ent(&mut self, cords: [i32; 2], ent: Entity) { *(self .pos_to_ent diff --git a/src/main.rs b/src/main.rs index ec552c0..e75431d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,28 @@ -#![allow(dead_code, unused_imports, unused_variables)] +#![allow(dead_code, unused_variables, unused_imports)] +mod add_break_blocks; mod block_reg; mod chunk; mod chunk_queue; mod player; +mod utils; -use bevy::prelude::*; +use add_break_blocks::*; +use bevy::{ + prelude::*, + tasks::{AsyncComputeTaskPool, ComputeTaskPool, Task}, +}; use bevy_meshem::prelude::*; use block_reg::*; use chunk::*; use chunk_queue::*; use futures_lite::future; -use noise::{NoiseFn, Perlin, Seedable}; +use noise::Perlin; use player::*; -use std::sync::Arc; +use std::{default, sync::Arc}; +pub use utils::*; -const FACTOR: usize = CHUNK_DIMS.0; +// const FACTOR: usize = CHUNK_DIMS.0; +// Render distance should be above 1. pub const RENDER_DISTANCE: i32 = 16; pub const GEN_SEED: u32 = 5; @@ -22,25 +30,51 @@ pub const GEN_SEED: u32 = 5; pub struct BlockMaterial(Handle); #[derive(Component)] -pub struct VertexCount(usize); +struct ToUpdate; + +#[derive(States, Clone, Default, PartialEq, Eq, Hash, Debug)] +pub enum InitialChunkLoadState { + #[default] + Loading, + MeshesLoaded, + Complete, +} + +#[derive(Component)] +struct LoadedChunks(usize); fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())); app.add_plugins(PlayerPlugin); + app.init_resource::(); app.init_resource::(); app.init_resource::(); - app.insert_resource(AmbientLight { brightness: 0.5, color: Color::WHITE, }); + app.add_event::(); + + app.add_state::(); + app.add_systems(PreStartup, setup); + app.add_systems( + PostUpdate, + update_closby_chunks.run_if(in_state(InitialChunkLoadState::Complete)), + ); + app.add_systems( + Update, + check_if_loaded.run_if(in_state(InitialChunkLoadState::MeshesLoaded)), + ); + app.add_systems(Update, update_mesh_frame); app.add_systems(Update, frame_chunk_update); app.add_systems(Update, handle_tasks); + app.add_systems(Update, add_break_detector); app.add_systems(PostUpdate, spawn_and_despawn_chunks); + app.add_systems(PostUpdate, handle_block_break_place); app.run(); } @@ -59,7 +93,7 @@ fn setup( ..default() }); commands.insert_resource(BlockMaterial(mat)); - commands.spawn(VertexCount(0)); + commands.spawn(LoadedChunks(0)); } fn frame_chunk_update( @@ -90,13 +124,6 @@ fn spawn_and_despawn_chunks( chunk_queue.queue_despawn(*chunk); } } - - // for u in -RENDER_DISTANCE + 1..=RENDER_DISTANCE { - // chunk_queue.queue_spawn([cords[0] + RENDER_DISTANCE, cords[1] + u]); - // chunk_queue.queue_spawn([cords[0] - RENDER_DISTANCE, cords[1] + u]); - // chunk_queue.queue_spawn([cords[0] + u, cords[1] + RENDER_DISTANCE]); - // chunk_queue.queue_spawn([cords[0] + u, cords[1] - RENDER_DISTANCE]); - // } for u in -RENDER_DISTANCE..=RENDER_DISTANCE { for v in -RENDER_DISTANCE..=RENDER_DISTANCE { chunk_queue.queue_spawn([cords[0] + u, cords[1] + v]); @@ -105,6 +132,32 @@ fn spawn_and_despawn_chunks( } } +fn update_closby_chunks( + current_chunk: Query<&CurrentChunk, Changed>, + chunk_map: Res, + mut commands: Commands, + old_close_chunks: Query>, +) { + if let Ok(cords) = current_chunk.get_single() { + let cords = cords.0; + for chunk in old_close_chunks.iter() { + commands.entity(chunk).remove::(); + } + + for i in -1..=1 { + for j in -1..=1 { + commands + .entity( + chunk_map + .get_ent([cords[0] + i, cords[1] + j]) + .expect("Chunk that was supposed to be loaded was not."), + ) + .insert(ChunkCloseToPlayer); + } + } + } +} + fn handle_tasks( mut commands: Commands, mut transform_tasks: Query<(Entity, &mut ComputeChunk)>, @@ -112,6 +165,9 @@ fn handle_tasks( mut meshes: ResMut>, mut chunk_map: ResMut, current_chunk: Query<&CurrentChunk>, + current_state: Res>, + mut loaded_chunks: Query<(Entity, &mut LoadedChunks)>, + mut next_state: ResMut>, ) { let cc = current_chunk .get_single() @@ -144,13 +200,119 @@ fn handle_tasks( ..default() }, Chunk { - compressed_chunk: vec![(0, 0)], + // compressed_chunk: vec![(0, 0)], + grid, cords, meta_data: metadata, }, + BlockChangeQueue { + block_queue: vec![], + }, )) .id(); chunk_map.insert_ent(cords, ent); + if let Ok((counter_ent, mut loaded_chunks)) = loaded_chunks.get_single_mut() { + match current_state.get() { + &InitialChunkLoadState::Loading => { + loaded_chunks.0 += 1; + if loaded_chunks.0 == (RENDER_DISTANCE * RENDER_DISTANCE) as usize { + next_state.set(InitialChunkLoadState::MeshesLoaded); + commands.entity(counter_ent).despawn(); + println!("\nInternal Log:\nMeshes have been loaded"); + } + } + _ => {} + } + } + } + } +} + +fn check_if_loaded( + mut next_state: ResMut>, + chunk_map: Res, + mut commands: Commands, +) { + for (chunk, ent) in chunk_map.iter() { + match commands.get_entity(*ent) { + None => return, + _ => {} } } + next_state.set(InitialChunkLoadState::Complete); + println!("\nInternal Log:\nChunk entities have been successfully spawned"); +} + +fn handle_block_break_place( + mut block_change: EventReader, + chunk_map: Res, + mut chunk_query: Query<(Entity, &mut Chunk), With>, + mut commands: Commands, +) { + 'outer: for event in block_change.iter() { + for &(chunk, block) in event.blocks.iter() { + let ent = chunk_map.get_ent(chunk).expect( + "Chunk should be loaded into internal data structure `ChunkMap` but it isn't.", + ); + for (e, mut c) in chunk_query.iter_mut() { + if e != ent { + continue; + } + assert_eq!(c.cords, chunk); + let tmp_neighbors: Vec> = vec![None; 6]; + let mut neighboring_voxels: [Option; 6] = [None; 6]; + + for i in 0..6 { + neighboring_voxels[i] = + if let Some(a) = get_neighbor(block, Face::from(i), CHUNK_DIMS) { + Some(c.grid[a]) + } else { + None + } + } + let vox = c.grid[block]; + + if vox == AIR && matches!(event.change, VoxelChange::Broken) { + break; + } + if vox != AIR && matches!(event.change, VoxelChange::Added) { + break; + } + + c.meta_data.log( + event.change, + block, + { + match event.change { + VoxelChange::Added => STONE, + VoxelChange::Broken => vox, + } + }, + neighboring_voxels, + ); + + match event.change { + VoxelChange::Added => c.grid[block] = STONE, + VoxelChange::Broken => c.grid[block] = AIR, + } + + commands.entity(e).insert(ToUpdate); + break 'outer; + } + } + } +} + +fn update_mesh_frame( + mut query: Query<(Entity, &Handle, &mut Chunk), With>, + mut meshes: ResMut>, + breg: Res, +) { + let breg = Arc::new(breg.into_inner().clone()); + for (ent, mesh_handle, mut chunk) in query.iter_mut() { + let mesh_ref_mut = meshes + .get_mut(mesh_handle) + .expect("Can't find chunk mesh in internal assets"); + update_mesh(mesh_ref_mut, &mut chunk.meta_data, &*breg.clone()); + } } diff --git a/src/player.rs b/src/player.rs index ac135d0..a0ed02d 100644 --- a/src/player.rs +++ b/src/player.rs @@ -4,11 +4,10 @@ // this code was taken from: // https://github.com/sburris0/bevy_flycam/tree/bevy_0.11 // ~~~~~~~~~~~~~~~~~~~ -use crate::chunk::{LENGTH, WIDTH}; -use crate::RENDER_DISTANCE; +use crate::utils::position_to_chunk; +use crate::*; use bevy::ecs::event::{Events, ManualEventReader}; use bevy::input::mouse::MouseMotion; -use bevy::prelude::*; use bevy::window::{CursorGrabMode, PrimaryWindow}; pub mod prelude { @@ -100,10 +99,11 @@ fn setup_player(mut commands: Commands) { Camera3dBundle { transform: Transform::from_xyz( // -RENDER_DISTANCE as f32 * WIDTH as f32, - 0.0, 650.0, // -RENDER_DISTANCE as f32 * LENGTH as f32, + 0.0, + HEIGHT as f32 * 2.0, // -RENDER_DISTANCE as f32 * LENGTH as f32, 0.0, ) - .looking_to(Vec3::new(0.0, -0.1, 0.0), Vec3::Y), + .looking_to(Vec3::new(5.0, -1.0, 5.0), Vec3::Y), ..Default::default() }, FlyCam, @@ -154,10 +154,7 @@ fn player_move( } let t = transform.translation; // find the current chunk we are in - let tmp = [ - (t.x / WIDTH as f32 + (t.x.signum() - 1.0) / 2.0) as i32, - (t.z / LENGTH as f32 + (t.z.signum() - 1.0) / 2.0) as i32, - ]; + let tmp = position_to_chunk(t, CHUNK_DIMS); if tmp != chunk.0 { chunk.0 = tmp; } @@ -240,9 +237,11 @@ impl Plugin for PlayerPlugin { .init_resource::() .add_systems(Startup, setup_player) .add_systems(Startup, initial_grab_cursor) - .add_systems(Update, player_move) - .add_systems(Update, player_look) - .add_systems(Update, cursor_grab); + .add_systems( + Update, + (player_move, player_look, cursor_grab) + .run_if(in_state(InitialChunkLoadState::Complete)), + ); } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..a208274 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,52 @@ +use bevy::prelude::Vec3; +pub fn position_to_chunk(pos: Vec3, chunk_dims: (usize, usize, usize)) -> [i32; 2] { + let chunk_width = chunk_dims.0; + let chunk_length = chunk_dims.1; + [ + (pos.x / chunk_width as f32 + (pos.x.signum() - 1.0) / 2.0) as i32, + (pos.z / chunk_length as f32 + (pos.z.signum() - 1.0) / 2.0) as i32, + ] +} + +// the bool is for whether or not the pos is within the height bounds +pub fn position_to_chunk_position( + pos: Vec3, + chunk_dims: (usize, usize, usize), +) -> ([i32; 2], [usize; 3], bool) { + let chunk_width = chunk_dims.0; + let chunk_length = chunk_dims.1; + let chunk_height = chunk_dims.2; + let chunk = position_to_chunk(pos, chunk_dims); + + let chunk_pos = [ + (pos.x - chunk[0] as f32 * chunk_width as f32) as usize, + pos.y as usize, + (pos.z - chunk[1] as f32 * chunk_length as f32) as usize, + ]; + + let flag = pos.y >= 0.0 && pos.y <= chunk_height as f32; + (chunk, chunk_pos, flag) +} + +pub fn three_d_cords(oned: usize, dims: (usize, usize, usize)) -> (usize, usize, usize) { + let height = dims.2; + let length = dims.1; + let width = dims.0; + + let h = (oned / (length * width)) as usize; + let l = ((oned - h * (length * width)) / width) as usize; + let w = (oned - h * (length * width) - l * width) as usize; + + assert!(w < width, "Out of bounds to convert into 3d coordinate."); + assert!(h < height, "Out of bounds to convert into 3d coordinate."); + assert!(l < length, "Out of bounds to convert into 3d coordinate."); + + (w, l, h) +} + +pub fn one_d_cords(threed: [usize; 3], dims: (usize, usize, usize)) -> usize { + assert!(threed[0] < dims.0, "3d coordinate out of dimension bounds."); + assert!(threed[1] < dims.1, "3d coordinate out of dimension bounds."); + assert!(threed[2] < dims.2, "3d coordinate out of dimension bounds."); + threed[2] * (dims.0 * dims.1) + threed[1] * dims.0 + threed[0] +}