diff --git a/kubi-shared/src/worldgen.rs b/kubi-shared/src/worldgen.rs index 5d6cc59..7ddb678 100644 --- a/kubi-shared/src/worldgen.rs +++ b/kubi-shared/src/worldgen.rs @@ -1,334 +1,342 @@ -use bracket_noise::prelude::*; -use rand::prelude::*; -use glam::{IVec3, ivec3, Vec3Swizzles, IVec2}; -use rand_xoshiro::Xoshiro256StarStar; -use crate::{ - chunk::{BlockData, CHUNK_SIZE}, - block::Block, - queue::QueuedBlock, -}; - -fn mountain_ramp(mut x: f32) -> f32 { - x *= 2.0; - if x < 0.4 { - 0.5 * x - } else if x < 0.55 { - 4. * (x - 0.4) + 0.2 - } else { - 0.4444 * (x - 0.55) + 0.8 - } -} - -fn local_height(height: i32, chunk_position: IVec3) -> usize { - let offset = chunk_position * CHUNK_SIZE as i32; - (height - offset.y).clamp(0, CHUNK_SIZE as i32) as usize -} - -fn local_y_position(height: i32, chunk_position: IVec3) -> Option { - let offset = chunk_position * CHUNK_SIZE as i32; - let position = height - offset.y; - (0..CHUNK_SIZE as i32).contains(&position).then_some(position as usize) -} - - -pub fn generate_world(chunk_position: IVec3, seed: u64) -> (BlockData, Vec) { - let offset = chunk_position * CHUNK_SIZE as i32; - let mut blocks = Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]); - let mut queue = Vec::with_capacity(0); - - let mut smart_place = |blocks: &mut BlockData, position: IVec3, block: Block| { - if position.to_array().iter().any(|&x| !(0..CHUNK_SIZE).contains(&(x as usize))) { - let event_pos = offset + position; - queue.retain(|block: &QueuedBlock| { - block.position != event_pos - }); - queue.push(QueuedBlock { - position: event_pos, - block_type: block, - soft: true - }); - } else { - blocks[position.x as usize][position.y as usize][position.z as usize] = block; - } - }; - - let mut height_noise = FastNoise::seeded(seed); - height_noise.set_fractal_type(FractalType::FBM); - height_noise.set_fractal_octaves(4); - height_noise.set_frequency(0.003); - - let mut elevation_noise = FastNoise::seeded(seed.rotate_left(1)); - elevation_noise.set_fractal_type(FractalType::FBM); - elevation_noise.set_fractal_octaves(1); - elevation_noise.set_frequency(0.001); - - let mut cave_noise_a = FastNoise::seeded(seed.rotate_left(2)); - cave_noise_a.set_fractal_type(FractalType::FBM); - cave_noise_a.set_fractal_octaves(2); - cave_noise_a.set_frequency(0.01); - - let mut cave_noise_b = FastNoise::seeded(seed.rotate_left(3)); - cave_noise_b.set_fractal_type(FractalType::FBM); - cave_noise_b.set_fractal_octaves(3); - cave_noise_b.set_frequency(0.015); - - let mut cave_noise_holes = FastNoise::seeded(seed.rotate_left(4)); - cave_noise_holes.set_fractal_type(FractalType::FBM); - cave_noise_holes.set_fractal_octaves(2); - cave_noise_holes.set_frequency(0.005); - - let mut ravine_nose_line = FastNoise::seeded(seed.rotate_left(5)); - ravine_nose_line.set_fractal_type(FractalType::Billow); - ravine_nose_line.set_fractal_octaves(2); - ravine_nose_line.set_frequency(0.005); - - let mut ravine_noise_location = FastNoise::seeded(seed.rotate_left(6)); - ravine_noise_location.set_fractal_type(FractalType::FBM); - ravine_noise_location.set_fractal_octaves(1); - ravine_noise_location.set_frequency(0.005); - - let mut river_noise = FastNoise::seeded(seed.rotate_left(7)); - river_noise.set_fractal_type(FractalType::Billow); - river_noise.set_fractal_octaves(2); - river_noise.set_frequency(0.5 * 0.005); - - let mut rng = Xoshiro256StarStar::seed_from_u64( - seed - ^ (chunk_position.x as u32 as u64) - ^ ((chunk_position.z as u32 as u64) << 32) - ); - let rng_map_a: [[f32; CHUNK_SIZE]; CHUNK_SIZE] = rng.gen(); - let rng_map_b: [[f32; CHUNK_SIZE]; CHUNK_SIZE] = rng.gen(); - - //Generate height map - let mut within_heightmap = false; - let mut deco_heightmap = [[None; CHUNK_SIZE]; CHUNK_SIZE]; - - for x in 0..CHUNK_SIZE { - for z in 0..CHUNK_SIZE { - let (noise_x, noise_y) = ((offset.x + x as i32) as f32, (offset.z + z as i32) as f32); - //sample noises (that are needed right now) - let raw_heightmap_value = height_noise.get_noise(noise_x, noise_y); - let raw_elevation_value = elevation_noise.get_noise(noise_x, noise_y); - let raw_ravine_location_value = ravine_noise_location.get_noise(noise_x, noise_y); - //compute height - let mut is_surface = true; - let mut river_fill_height = None; - let height = { - let local_elevation = raw_elevation_value.powi(4).sqrt(); - let mut height = (mountain_ramp(raw_heightmap_value) * local_elevation * 100.) as i32; - //Flatten valleys - if height < 0 { - height /= 2; - } - //Generate rivers - { - let river_width = (height as f32 / -5.).clamp(0.5, 1.); - let river_value = river_noise.get_noise(noise_x, noise_y); - if ((-0.00625 * river_width)..(0.00625 * river_width)).contains(&(river_value.powi(2))) { - is_surface = false; - river_fill_height = Some(height - 1); - //river_fill_height = Some(-3); - height -= (river_width * 15. * ((0.00625 * river_width) - river_value.powi(2)) * (1. / (0.00625 * river_width))).round() as i32; - } - } - //Generate ravines - if height < 0 && raw_ravine_location_value > 0.4 { - let raw_ravine_value = ravine_nose_line.get_noise(noise_x, noise_y); - if (-0.0125..0.0125).contains(&(raw_ravine_value.powi(2))) { - is_surface = false; - height -= (100. * (0.0125 - raw_ravine_value.powi(2)) * (1. / 0.0125)).round() as i32; - } - } - height - }; - //add to heightmap - if is_surface { - deco_heightmap[x][z] = Some(height); - //place dirt - for y in 0..local_height(height, chunk_position) { - blocks[x][y][z] = Block::Dirt; - within_heightmap = true; - } - //place stone - for y in 0..local_height(height - 5 - (raw_heightmap_value * 5.) as i32, chunk_position) { - blocks[x][y][z] = Block::Stone; - within_heightmap = true; - } - //place grass - if let Some(y) = local_y_position(height, chunk_position) { - blocks[x][y][z] = Block::Grass; - within_heightmap = true; - } - } else if let Some(river_fill_height) = river_fill_height { - //Place water - for y in 0..local_height(river_fill_height, chunk_position) { - blocks[x][y][z] = Block::Water; - within_heightmap = true; - } - //Place stone - for y in 0..local_height(height, chunk_position) { - blocks[x][y][z] = Block::Stone; - within_heightmap = true; - } - //Place dirt - if let Some(y) = local_y_position(height, chunk_position) { - blocks[x][y][z] = Block::Dirt; - within_heightmap = true; - } - } else { - //Place stone - for y in 0..local_height(height, chunk_position) { - blocks[x][y][z] = Block::Stone; - within_heightmap = true; - } - } - } - } - - //Carve out caves - if within_heightmap { - for z in 0..CHUNK_SIZE { - for y in 0..CHUNK_SIZE { - for x in 0..CHUNK_SIZE { - if blocks[x][y][z] != Block::Stone { continue } - - let cave_size = ((offset.y + y as i32) as f32 / -100.).clamp(0., 1.); - let inv_cave_size = 1. - cave_size; - if cave_size < 0.1 { continue } - - let position = ivec3(x as i32, y as i32, z as i32) + offset; - - let is_cave = || { - let raw_cavemap_value_a = cave_noise_a.get_noise3d(position.x as f32, position.y as f32, position.z as f32); - let raw_cavemap_value_b = cave_noise_b.get_noise3d(position.x as f32, position.y as f32, position.z as f32); - ((cave_size * -0.15)..=(cave_size * 0.15)).contains(&raw_cavemap_value_a) && - ((cave_size * -0.15)..=(cave_size * 0.15)).contains(&raw_cavemap_value_b) - }; - let is_hole_cave = || { - let raw_cavemap_value_holes = cave_noise_holes.get_noise3d(position.x as f32, position.y as f32, position.z as f32); - ((0.9 + (0.1 * inv_cave_size))..=1.0).contains(&raw_cavemap_value_holes.abs()) - }; - - if is_cave() || is_hole_cave() { - blocks[x][y][z] = Block::Air; - if deco_heightmap[x][z] == Some(y as i32 + offset.y) { - deco_heightmap[x][z] = None - } - } - } - } - } - } - - //Add decorations - for x in 0..CHUNK_SIZE { - for z in 0..CHUNK_SIZE { - //get height - let Some(height) = deco_heightmap[x][z] else { continue }; - //check for air - // if blocks[x][local_y][z] == Block::Air { - // continue - // } - //place tall grass - if rng_map_a[x][z] < 0.03 { - if let Some(y) = local_y_position(height + 1, chunk_position) { - blocks[x][y][z] = Block::TallGrass; - } - } - //place trees! - if rng_map_a[x][z] < 0.001 { - //Replace grass with dirt under the tree - if let Some(y) = local_y_position(height, chunk_position) { - blocks[x][y][z] = Block::Dirt; - } - - //Place wood (no smart_place needed here!) - let tree_height = 4 + (rng_map_b[x][z] * 3.).round() as i32; - for tree_y in 0..tree_height { - if let Some(y) = local_y_position(height + 1 + tree_y, chunk_position) { - blocks[x][y][z] = Block::Wood; - } - } - - let tree_height = 4 + (rng_map_b[x][z] * 3.).round() as i32; - - //Place leaf blocks - if let Some(y) = local_y_position(height + 1, chunk_position) { - let tree_pos = ivec3(x as i32, y as i32, z as i32); - // Place wood (smart_place) - // for tree_y in 0..tree_height { - // smart_place(&mut blocks, tree_pos + tree_y * IVec3::Y, Block::Wood); - // } - // Part that wraps around the tree - { - let tree_leaf_height = tree_height - 3; - let leaf_width = 2; - for tree_y in tree_leaf_height..tree_height { - for tree_x in (-leaf_width)..=leaf_width { - for tree_z in (-leaf_width)..=leaf_width { - let tree_offset = ivec3(tree_x, tree_y, tree_z); - if tree_offset.xz() == IVec2::ZERO { continue } - smart_place(&mut blocks, tree_pos + tree_offset, Block::Leaf); - } - } - } - } - //part above the tree - { - let leaf_above_height = 2; - let leaf_width = 1; - for tree_y in tree_height..(tree_height + leaf_above_height) { - for tree_x in (-leaf_width)..=leaf_width { - for tree_z in (-leaf_width)..=leaf_width { - let tree_offset = ivec3(tree_x, tree_y, tree_z); - smart_place(&mut blocks, tree_pos + tree_offset, Block::Leaf); - } - } - } - } - } - } - } - } - - (blocks, queue) - - // let mut cave_noise = FastNoise::seeded(seed); - // cave_noise.set_fractal_type(FractalType::FBM); - // cave_noise.set_frequency(0.1); - - // let mut dirt_noise = FastNoise::seeded(seed.rotate_left(1)); - // dirt_noise.set_fractal_type(FractalType::FBM); - // dirt_noise.set_frequency(0.1); - - // - - // if chunk_position.y >= 0 { - // if chunk_position.y == 0 { - // for x in 0..CHUNK_SIZE { - // for z in 0..CHUNK_SIZE { - // blocks[x][0][z] = Block::Dirt; - // blocks[x][1][z] = Block::Grass; - // } - // } - // } - // } else { - // for x in 0..CHUNK_SIZE { - // for y in 0..CHUNK_SIZE { - // for z in 0..CHUNK_SIZE { - // let position = ivec3(x as i32, y as i32, z as i32) + offset; - // let v_cave_noise = cave_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32 - 10.0).clamp(0., 1.); - // let v_dirt_noise = dirt_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32).clamp(0., 1.); - // if v_cave_noise > 0.5 { - // blocks[x][y][z] = Block::Stone; - // } else if v_dirt_noise > 0.5 { - // blocks[x][y][z] = Block::Dirt; - // } - // } - // } - // } - // } - // blocks - -} +use bracket_noise::prelude::*; +use rand::prelude::*; +use glam::{IVec3, ivec3, Vec3Swizzles, IVec2}; +use rand_xoshiro::Xoshiro256StarStar; +use crate::{ + chunk::{BlockData, CHUNK_SIZE}, + block::Block, + queue::QueuedBlock, +}; + +fn mountain_ramp(mut x: f32) -> f32 { + x *= 2.0; + if x < 0.4 { + 0.5 * x + } else if x < 0.55 { + 4. * (x - 0.4) + 0.2 + } else { + 0.4444 * (x - 0.55) + 0.8 + } +} + +fn local_height(height: i32, chunk_position: IVec3) -> usize { + let offset = chunk_position * CHUNK_SIZE as i32; + (height - offset.y).clamp(0, CHUNK_SIZE as i32) as usize +} + +fn local_y_position(height: i32, chunk_position: IVec3) -> Option { + let offset = chunk_position * CHUNK_SIZE as i32; + let position = height - offset.y; + (0..CHUNK_SIZE as i32).contains(&position).then_some(position as usize) +} + + +pub fn generate_world(chunk_position: IVec3, seed: u64) -> (BlockData, Vec) { + let offset = chunk_position * CHUNK_SIZE as i32; + let mut blocks = Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]); + let mut queue = Vec::with_capacity(0); + + let mut smart_place = |blocks: &mut BlockData, position: IVec3, block: Block| { + if position.to_array().iter().any(|&x| !(0..CHUNK_SIZE).contains(&(x as usize))) { + let event_pos = offset + position; + queue.retain(|block: &QueuedBlock| { + block.position != event_pos + }); + queue.push(QueuedBlock { + position: event_pos, + block_type: block, + soft: true + }); + } else { + blocks[position.x as usize][position.y as usize][position.z as usize] = block; + } + }; + + //STICK + if chunk_position.x == 0 && chunk_position.y == 5 { + for z in 0..CHUNK_SIZE { + blocks[0][0][z] = Block::Stone; + } + } + // + + let mut height_noise = FastNoise::seeded(seed); + height_noise.set_fractal_type(FractalType::FBM); + height_noise.set_fractal_octaves(4); + height_noise.set_frequency(0.003); + + let mut elevation_noise = FastNoise::seeded(seed.rotate_left(1)); + elevation_noise.set_fractal_type(FractalType::FBM); + elevation_noise.set_fractal_octaves(1); + elevation_noise.set_frequency(0.001); + + let mut cave_noise_a = FastNoise::seeded(seed.rotate_left(2)); + cave_noise_a.set_fractal_type(FractalType::FBM); + cave_noise_a.set_fractal_octaves(2); + cave_noise_a.set_frequency(0.01); + + let mut cave_noise_b = FastNoise::seeded(seed.rotate_left(3)); + cave_noise_b.set_fractal_type(FractalType::FBM); + cave_noise_b.set_fractal_octaves(3); + cave_noise_b.set_frequency(0.015); + + let mut cave_noise_holes = FastNoise::seeded(seed.rotate_left(4)); + cave_noise_holes.set_fractal_type(FractalType::FBM); + cave_noise_holes.set_fractal_octaves(2); + cave_noise_holes.set_frequency(0.005); + + let mut ravine_nose_line = FastNoise::seeded(seed.rotate_left(5)); + ravine_nose_line.set_fractal_type(FractalType::Billow); + ravine_nose_line.set_fractal_octaves(2); + ravine_nose_line.set_frequency(0.005); + + let mut ravine_noise_location = FastNoise::seeded(seed.rotate_left(6)); + ravine_noise_location.set_fractal_type(FractalType::FBM); + ravine_noise_location.set_fractal_octaves(1); + ravine_noise_location.set_frequency(0.005); + + let mut river_noise = FastNoise::seeded(seed.rotate_left(7)); + river_noise.set_fractal_type(FractalType::Billow); + river_noise.set_fractal_octaves(2); + river_noise.set_frequency(0.5 * 0.005); + + let mut rng = Xoshiro256StarStar::seed_from_u64( + seed + ^ (chunk_position.x as u32 as u64) + ^ ((chunk_position.z as u32 as u64) << 32) + ); + let rng_map_a: [[f32; CHUNK_SIZE]; CHUNK_SIZE] = rng.gen(); + let rng_map_b: [[f32; CHUNK_SIZE]; CHUNK_SIZE] = rng.gen(); + + //Generate height map + let mut within_heightmap = false; + let mut deco_heightmap = [[None; CHUNK_SIZE]; CHUNK_SIZE]; + + for x in 0..CHUNK_SIZE { + for z in 0..CHUNK_SIZE { + let (noise_x, noise_y) = ((offset.x + x as i32) as f32, (offset.z + z as i32) as f32); + //sample noises (that are needed right now) + let raw_heightmap_value = height_noise.get_noise(noise_x, noise_y); + let raw_elevation_value = elevation_noise.get_noise(noise_x, noise_y); + let raw_ravine_location_value = ravine_noise_location.get_noise(noise_x, noise_y); + //compute height + let mut is_surface = true; + let mut river_fill_height = None; + let height = { + let local_elevation = raw_elevation_value.powi(4).sqrt(); + let mut height = (mountain_ramp(raw_heightmap_value) * local_elevation * 100.) as i32; + //Flatten valleys + if height < 0 { + height /= 2; + } + //Generate rivers + { + let river_width = (height as f32 / -5.).clamp(0.5, 1.); + let river_value = river_noise.get_noise(noise_x, noise_y); + if ((-0.00625 * river_width)..(0.00625 * river_width)).contains(&(river_value.powi(2))) { + is_surface = false; + river_fill_height = Some(height - 1); + //river_fill_height = Some(-3); + height -= (river_width * 15. * ((0.00625 * river_width) - river_value.powi(2)) * (1. / (0.00625 * river_width))).round() as i32; + } + } + //Generate ravines + if height < 0 && raw_ravine_location_value > 0.4 { + let raw_ravine_value = ravine_nose_line.get_noise(noise_x, noise_y); + if (-0.0125..0.0125).contains(&(raw_ravine_value.powi(2))) { + is_surface = false; + height -= (100. * (0.0125 - raw_ravine_value.powi(2)) * (1. / 0.0125)).round() as i32; + } + } + height + }; + //add to heightmap + if is_surface { + deco_heightmap[x][z] = Some(height); + //place dirt + for y in 0..local_height(height, chunk_position) { + blocks[x][y][z] = Block::Dirt; + within_heightmap = true; + } + //place stone + for y in 0..local_height(height - 5 - (raw_heightmap_value * 5.) as i32, chunk_position) { + blocks[x][y][z] = Block::Stone; + within_heightmap = true; + } + //place grass + if let Some(y) = local_y_position(height, chunk_position) { + blocks[x][y][z] = Block::Grass; + within_heightmap = true; + } + } else if let Some(river_fill_height) = river_fill_height { + //Place water + for y in 0..local_height(river_fill_height, chunk_position) { + blocks[x][y][z] = Block::Water; + within_heightmap = true; + } + //Place stone + for y in 0..local_height(height, chunk_position) { + blocks[x][y][z] = Block::Stone; + within_heightmap = true; + } + //Place dirt + if let Some(y) = local_y_position(height, chunk_position) { + blocks[x][y][z] = Block::Dirt; + within_heightmap = true; + } + } else { + //Place stone + for y in 0..local_height(height, chunk_position) { + blocks[x][y][z] = Block::Stone; + within_heightmap = true; + } + } + } + } + + //Carve out caves + if within_heightmap { + for z in 0..CHUNK_SIZE { + for y in 0..CHUNK_SIZE { + for x in 0..CHUNK_SIZE { + if blocks[x][y][z] != Block::Stone { continue } + + let cave_size = ((offset.y + y as i32) as f32 / -100.).clamp(0., 1.); + let inv_cave_size = 1. - cave_size; + if cave_size < 0.1 { continue } + + let position = ivec3(x as i32, y as i32, z as i32) + offset; + + let is_cave = || { + let raw_cavemap_value_a = cave_noise_a.get_noise3d(position.x as f32, position.y as f32, position.z as f32); + let raw_cavemap_value_b = cave_noise_b.get_noise3d(position.x as f32, position.y as f32, position.z as f32); + ((cave_size * -0.15)..=(cave_size * 0.15)).contains(&raw_cavemap_value_a) && + ((cave_size * -0.15)..=(cave_size * 0.15)).contains(&raw_cavemap_value_b) + }; + let is_hole_cave = || { + let raw_cavemap_value_holes = cave_noise_holes.get_noise3d(position.x as f32, position.y as f32, position.z as f32); + ((0.9 + (0.1 * inv_cave_size))..=1.0).contains(&raw_cavemap_value_holes.abs()) + }; + + if is_cave() || is_hole_cave() { + blocks[x][y][z] = Block::Air; + if deco_heightmap[x][z] == Some(y as i32 + offset.y) { + deco_heightmap[x][z] = None + } + } + } + } + } + } + + //Add decorations + for x in 0..CHUNK_SIZE { + for z in 0..CHUNK_SIZE { + //get height + let Some(height) = deco_heightmap[x][z] else { continue }; + //check for air + // if blocks[x][local_y][z] == Block::Air { + // continue + // } + //place tall grass + if rng_map_a[x][z] < 0.03 { + if let Some(y) = local_y_position(height + 1, chunk_position) { + blocks[x][y][z] = Block::TallGrass; + } + } + //place trees! + if rng_map_a[x][z] < 0.001 { + //Replace grass with dirt under the tree + if let Some(y) = local_y_position(height, chunk_position) { + blocks[x][y][z] = Block::Dirt; + } + + //Place wood (no smart_place needed here!) + let tree_height = 4 + (rng_map_b[x][z] * 3.).round() as i32; + for tree_y in 0..tree_height { + if let Some(y) = local_y_position(height + 1 + tree_y, chunk_position) { + blocks[x][y][z] = Block::Wood; + } + } + + let tree_height = 4 + (rng_map_b[x][z] * 3.).round() as i32; + + //Place leaf blocks + if let Some(y) = local_y_position(height + 1, chunk_position) { + let tree_pos = ivec3(x as i32, y as i32, z as i32); + // Place wood (smart_place) + // for tree_y in 0..tree_height { + // smart_place(&mut blocks, tree_pos + tree_y * IVec3::Y, Block::Wood); + // } + // Part that wraps around the tree + { + let tree_leaf_height = tree_height - 3; + let leaf_width = 2; + for tree_y in tree_leaf_height..tree_height { + for tree_x in (-leaf_width)..=leaf_width { + for tree_z in (-leaf_width)..=leaf_width { + let tree_offset = ivec3(tree_x, tree_y, tree_z); + if tree_offset.xz() == IVec2::ZERO { continue } + smart_place(&mut blocks, tree_pos + tree_offset, Block::Leaf); + } + } + } + } + //part above the tree + { + let leaf_above_height = 2; + let leaf_width = 1; + for tree_y in tree_height..(tree_height + leaf_above_height) { + for tree_x in (-leaf_width)..=leaf_width { + for tree_z in (-leaf_width)..=leaf_width { + let tree_offset = ivec3(tree_x, tree_y, tree_z); + smart_place(&mut blocks, tree_pos + tree_offset, Block::Leaf); + } + } + } + } + } + } + } + } + + (blocks, queue) + + // let mut cave_noise = FastNoise::seeded(seed); + // cave_noise.set_fractal_type(FractalType::FBM); + // cave_noise.set_frequency(0.1); + + // let mut dirt_noise = FastNoise::seeded(seed.rotate_left(1)); + // dirt_noise.set_fractal_type(FractalType::FBM); + // dirt_noise.set_frequency(0.1); + + // + + // if chunk_position.y >= 0 { + // if chunk_position.y == 0 { + // for x in 0..CHUNK_SIZE { + // for z in 0..CHUNK_SIZE { + // blocks[x][0][z] = Block::Dirt; + // blocks[x][1][z] = Block::Grass; + // } + // } + // } + // } else { + // for x in 0..CHUNK_SIZE { + // for y in 0..CHUNK_SIZE { + // for z in 0..CHUNK_SIZE { + // let position = ivec3(x as i32, y as i32, z as i32) + offset; + // let v_cave_noise = cave_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32 - 10.0).clamp(0., 1.); + // let v_dirt_noise = dirt_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32).clamp(0., 1.); + // if v_cave_noise > 0.5 { + // blocks[x][y][z] = Block::Stone; + // } else if v_dirt_noise > 0.5 { + // blocks[x][y][z] = Block::Dirt; + // } + // } + // } + // } + // } + // blocks + +} diff --git a/kubi/src/client_physics.rs b/kubi/src/client_physics.rs index cacbaf1..ac79175 100644 --- a/kubi/src/client_physics.rs +++ b/kubi/src/client_physics.rs @@ -1,49 +1,185 @@ //TODO client-side physics //TODO move this to shared -use glam::{Mat4, Vec3}; -use kubi_shared::transform::Transform; +use glam::{vec3, IVec3, Mat4, Vec3, Vec3Swizzles}; use shipyard::{track, AllStoragesView, Component, IntoIter, Unique, UniqueView, View, ViewMut}; -use crate::delta_time::DeltaTime; +use kubi_shared::{block::{Block, CollisionType}, transform::Transform}; +use crate::{delta_time::DeltaTime, world::ChunkStorage}; #[derive(Unique)] pub struct GlobalClPhysicsConfig { pub gravity: Vec3, + ///XXX: currenly unused: + pub iterations: usize, } +impl Default for GlobalClPhysicsConfig { + fn default() -> Self { + Self { + gravity: Vec3::new(0., -9.8, 0.), + iterations: 10, + } + } +} + +//TODO: actors should be represented by a vertical line, not a point. +//XXX: maybe a capsule? (or configurable hull?) +//TODO: per block friction + #[derive(Component)] pub struct ClPhysicsActor { + pub disable: bool, + pub offset: Vec3, pub forces: Vec3, + pub frame_velocity: Vec3, pub velocity: Vec3, - pub terminal_velocity: f32, - //TODO: this should be configurable per block - pub friction_agains_ground: f32, + pub decel: Vec3, + pub gravity_scale: f32, + pub max_velocity: (Option, Option, Option), + pub hack_xz_circular: bool, + flag_ground: bool, + flag_collision: bool, +} + +impl ClPhysicsActor { + pub fn apply_force(&mut self, force: Vec3) { + self.forces += force; + } + + pub fn add_frame_velocity(&mut self, force: Vec3) { + self.frame_velocity += force; + } + + pub fn on_ground(&self) -> bool { + self.flag_ground + } } impl Default for ClPhysicsActor { fn default() -> Self { Self { + //HACK: for player + disable: false, + offset: vec3(0., 1.5, 0.), forces: Vec3::ZERO, + frame_velocity: Vec3::ZERO, velocity: Vec3::ZERO, - terminal_velocity: 40., - friction_agains_ground: 0.5, + //constant deceleration, in ratio per second. e.g. value of 1 should stop the actor in 1 second. + decel: vec3(1., 0., 1.), + gravity_scale: 1., + max_velocity: (Some(20.), None, Some(20.)), + hack_xz_circular: true, + flag_ground: false, + flag_collision: false, } } } +trait BlockCollisionExt { + fn collision_type(&self) -> CollisionType; + fn is_solid(&self) -> bool { + self.collision_type() == CollisionType::Solid + } +} + +impl BlockCollisionExt for Option { + fn collision_type(&self) -> CollisionType { + self.unwrap_or(Block::Air).descriptor().collision + } +} + +impl BlockCollisionExt for Block { + fn collision_type(&self) -> CollisionType { + self.descriptor().collision + } +} + pub fn init_client_physics( storages: AllStoragesView, ) { - storages.add_unique(GlobalClPhysicsConfig { - gravity: Vec3::new(0., -9.8, 0.), - }); + storages.add_unique(GlobalClPhysicsConfig::default()); } pub fn update_client_physics_late( - controllers: View, + mut actors: ViewMut, mut transforms: ViewMut, + conf: UniqueView, + world: UniqueView, dt: UniqueView, - phy_conf: UniqueView, ) { + for (mut actor, mut transform) in (&mut actors, &mut transforms).iter() { + if actor.disable { + actor.forces = Vec3::ZERO; + continue; + } + + //apply forces + let actor_forces = actor.forces; + actor.velocity += (actor_forces + conf.gravity) * dt.0.as_secs_f32(); + actor.forces = Vec3::ZERO; + + //get position + let (scale, rotation, mut actor_position) = transform.0.to_scale_rotation_translation(); + actor_position -= actor.offset; + + //get grid-aligned pos and blocks + let actor_block_pos = actor_position.floor().as_ivec3(); + let actor_block = world.get_block(actor_block_pos); + let actor_block_pos_slightly_below = (actor_position + Vec3::NEG_Y * 0.01).floor().as_ivec3(); + let actor_block_below = world.get_block(actor_block_pos_slightly_below); + + //update flags + actor.flag_collision = actor_block.is_solid(); + actor.flag_ground = actor.flag_collision || actor_block_below.is_solid(); + + //push actor back out of the block + if actor.flag_collision { + //first, compute restitution, based on position inside the block + // let block_center = actor_block_pos.as_f32() + Vec3::ONE * 0.5; + // let to_block_center = actor_position - block_center; + + //then, based on normal: + //push the actor back + //actor_position += normal * 0.5; + //cancel out velocity in the direction of the normal + // let dot = actor.velocity.dot(normal); + // if dot > 0. { + // //actor.velocity -= normal * dot; + // actor.velocity = Vec3::ZERO; + // } + + //HACK: for now, just stop the vertical velocity if on ground altogether, + //as we don't have proper collision velocity resolution yet (we need to compute dot product or sth) + if actor.flag_ground { + actor.velocity.y = actor.velocity.y.max(0.); + } + } + + //clamp velocity + let max_velocity = actor.max_velocity; + if actor.hack_xz_circular && actor.max_velocity.0.is_some() && (actor.max_velocity.0 == actor.max_velocity.2) { + actor.velocity.y = actor.velocity.y.clamp(-max_velocity.1.unwrap_or(f32::MAX), max_velocity.1.unwrap_or(f32::MAX)); + let clamped = actor.velocity.xz().clamp_length_max(actor.max_velocity.0.unwrap_or(f32::MAX)); + actor.velocity.x = clamped.x; + actor.velocity.z = clamped.y; + } else { + actor.velocity = vec3( + actor.velocity.x.clamp(-max_velocity.0.unwrap_or(f32::MAX), max_velocity.0.unwrap_or(f32::MAX)), + actor.velocity.y.clamp(-max_velocity.1.unwrap_or(f32::MAX), max_velocity.1.unwrap_or(f32::MAX)), + actor.velocity.z.clamp(-max_velocity.2.unwrap_or(f32::MAX), max_velocity.2.unwrap_or(f32::MAX)), + ); + } + + //Apply velocity + actor_position += (actor.velocity + actor.frame_velocity) * dt.0.as_secs_f32(); + actor.frame_velocity = Vec3::ZERO; + actor_position += actor.offset; + transform.0 = Mat4::from_scale_rotation_translation(scale, rotation.normalize(), actor_position); + + //Apply "friction" + let actor_velocity = actor.velocity; + let actor_decel = actor.decel; + actor.velocity -= actor_velocity * actor_decel * dt.0.as_secs_f32(); + } // for (_, mut transform) in (&controllers, &mut transforms).iter() { // let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation(); // translation.y -= dt.0.as_secs_f32() * 100.; diff --git a/kubi/src/events/player_actions.rs b/kubi/src/events/player_actions.rs index 564b6aa..6946962 100644 --- a/kubi/src/events/player_actions.rs +++ b/kubi/src/events/player_actions.rs @@ -1,35 +1,39 @@ -use shipyard::{Component, View, ViewMut, EntitiesViewMut, IntoIter, track}; -use glam::{IVec3, Quat, Vec3}; -use kubi_shared::block::Block; -use crate::{ - player::MainPlayer, - transform::Transform -}; -use super::EventComponent; - -#[derive(Component, Clone, Copy, Debug)] -pub enum PlayerActionEvent { - PositionChanged { - position: Vec3, - direction: Quat - }, - UpdatedBlock { - position: IVec3, - block: Block, - }, -} - -pub fn generate_move_events( - transforms: View, - player: View, - mut entities: EntitiesViewMut, - mut events: ViewMut, - mut actions: ViewMut, -) { - let Some((_, transform)) = (&player, transforms.inserted_or_modified()).iter().next() else { return }; - let (_, direction, position) = transform.0.to_scale_rotation_translation(); - entities.add_entity( - (&mut events, &mut actions), - (EventComponent, PlayerActionEvent::PositionChanged { position, direction }) - ); -} +use shipyard::{Component, View, ViewMut, EntitiesViewMut, IntoIter, track}; +use glam::{IVec3, Quat, Vec3}; +use kubi_shared::block::Block; +use crate::{ + client_physics::ClPhysicsActor, player::MainPlayer, transform::Transform +}; +use super::EventComponent; + +#[derive(Component, Clone, Copy, Debug)] +pub enum PlayerActionEvent { + PositionChanged { + position: Vec3, + //XXX: should this even be here? + velocity: Vec3, + direction: Quat + }, + UpdatedBlock { + position: IVec3, + block: Block, + }, +} + +pub fn generate_move_events( + transforms: View, + player: View, + actors: View, + mut entities: EntitiesViewMut, + mut events: ViewMut, + mut actions: ViewMut, +) { + let Some((_, transform, actor)) = (&player, transforms.inserted_or_modified(), &actors).iter().next() else { return }; + let (_, direction, position) = transform.0.to_scale_rotation_translation(); + //HACK: if the actor is disabled, the velocity is irrelevant, so we just set it to zero. + let velocity = if actor.disable { Vec3::ZERO } else { actor.velocity }; + entities.add_entity( + (&mut events, &mut actions), + (EventComponent, PlayerActionEvent::PositionChanged { position, velocity, direction }) + ); +} diff --git a/kubi/src/fly_controller.rs b/kubi/src/fly_controller.rs deleted file mode 100644 index c13f760..0000000 --- a/kubi/src/fly_controller.rs +++ /dev/null @@ -1,53 +0,0 @@ -use glam::{Vec3, Mat4, Quat, EulerRot, Vec2}; -use shipyard::{Component, View, ViewMut, IntoIter, UniqueView, Workload, IntoWorkload, track}; -use std::f32::consts::PI; -use crate::{transform::Transform, input::Inputs, settings::GameSettings, delta_time::DeltaTime}; - -#[derive(Component)] -pub struct FlyController; - -pub fn update_controllers() -> Workload { - ( - update_look, - update_movement - ).into_sequential_workload() -} - -const MAX_PITCH: f32 = PI/2. - 0.05; - -fn update_look( - controllers: View, - mut transforms: ViewMut, - inputs: UniqueView, - settings: UniqueView, - dt: UniqueView, -) { - let look = inputs.look * settings.mouse_sensitivity * dt.0.as_secs_f32(); - if look == Vec2::ZERO { return } - for (_, mut transform) in (&controllers, &mut transforms).iter() { - let (scale, mut rotation, translation) = transform.0.to_scale_rotation_translation(); - let (mut yaw, mut pitch, _roll) = rotation.to_euler(EulerRot::YXZ); - yaw -= look.x; - pitch -= look.y; - pitch = pitch.clamp(-MAX_PITCH, MAX_PITCH); - rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.).normalize(); - transform.0 = Mat4::from_scale_rotation_translation(scale, rotation, translation); - } -} - -fn update_movement( - controllers: View, - mut transforms: ViewMut, - inputs: UniqueView, - dt: UniqueView, -) { - if inputs.movement == Vec2::ZERO { return } - let movement = inputs.movement * 30. * dt.0.as_secs_f32(); - for (_, mut transform) in (&controllers, &mut transforms).iter() { - let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation(); - let rotation_norm = rotation.normalize(); - translation += (rotation_norm * Vec3::NEG_Z).normalize() * movement.y; - translation += (rotation_norm * Vec3::X).normalize() * movement.x; - transform.0 = Mat4::from_scale_rotation_translation(scale, rotation_norm, translation); - } -} diff --git a/kubi/src/input.rs b/kubi/src/input.rs index 406118b..c24081e 100644 --- a/kubi/src/input.rs +++ b/kubi/src/input.rs @@ -1,297 +1,301 @@ -use gilrs::{Gilrs, GamepadId, Button, Event, Axis}; -use glam::{Vec2, DVec2, vec2, dvec2}; -use winit::{ - keyboard::{KeyCode, PhysicalKey}, - event::{DeviceEvent, DeviceId, ElementState, TouchPhase} -}; -use hashbrown::HashMap; -use tinyset::{SetU32, SetU64}; -use nohash_hasher::BuildNoHashHasher; -use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView, NonSendSync}; -use crate::{ - events::{InputDeviceEvent, TouchEvent}, - rendering::WindowSize -}; - -#[derive(Unique, Clone, Copy, Default, Debug)] -pub struct Inputs { - pub movement: Vec2, - pub look: Vec2, - pub action_a: bool, - pub action_b: bool, -} - -#[derive(Unique, Clone, Copy, Default, Debug)] -pub struct PrevInputs(pub Inputs); - -#[derive(Unique, Clone, Default, Debug)] -pub struct RawKbmInputState { - pub keyboard_state: SetU32, - pub button_state: [bool; 32], - pub mouse_delta: DVec2 -} - -#[derive(Clone, Copy, Debug, Default)] -pub enum FingerCheck { - #[default] - Start, - Current, - StartOrCurrent, - StartAndCurrent, - NotMoved, -} - -#[derive(Clone, Copy, Debug)] -pub struct Finger { - pub id: u64, - pub device_id: DeviceId, - pub prev_position: DVec2, - pub start_position: DVec2, - pub current_position: DVec2, - pub has_moved: bool, -} -impl Finger { - pub fn within_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> bool { - let within_area = |pos: DVec2| -> bool { - ((pos - area_pos).min_element() >= 0.) && - ((pos - (area_pos + area_size)).max_element() <= 0.) - }; - let start = within_area(self.start_position); - let current = within_area(self.current_position); - match check { - FingerCheck::Start => start, - FingerCheck::Current => current, - FingerCheck::StartOrCurrent => start || current, - FingerCheck::StartAndCurrent => start && current, - FingerCheck::NotMoved => current && !self.has_moved, - } - } -} - -#[derive(Unique, Clone, Default, Debug)] -pub struct RawTouchState { - //TODO: handle multiple touch devices somehow - pub fingers: HashMap> -} - -impl RawTouchState { - pub fn query_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> impl Iterator + '_ { - self.fingers.iter().filter_map(move |(_, &finger)| { - finger.within_area(area_pos, area_size, check).then_some(finger) - }) - } -} - -#[derive(Unique)] -pub struct GilrsWrapper(Option); - -#[derive(Unique, Default, Clone, Copy)] -pub struct ActiveGamepad(Option); - -//maybe we should manage gamepad state ourselves just like keyboard? -//at least for the sake of consitency - -fn process_events( - device_events: View, - mut input_state: UniqueViewMut, -) { - input_state.mouse_delta = DVec2::ZERO; - for event in device_events.iter() { - match &event.event { - DeviceEvent::MouseMotion { delta } => { - input_state.mouse_delta = DVec2::from(*delta); - }, - DeviceEvent::Key(input) => { - if let PhysicalKey::Code(code) = input.physical_key { - match input.state { - ElementState::Pressed => input_state.keyboard_state.insert(code as u32), - ElementState::Released => input_state.keyboard_state.remove(code as u32), - }; - } - }, - DeviceEvent::Button { button, state } => { - if *button < 32 { - input_state.button_state[*button as usize] = matches!(*state, ElementState::Pressed); - } - }, - _ => () - } - } -} - -fn process_touch_events( - touch_events: View, - mut touch_state: UniqueViewMut, -) { - for (_, finger) in &mut touch_state.fingers { - finger.prev_position = finger.current_position; - } - for event in touch_events.iter() { - let position = dvec2(event.0.location.x, event.0.location.y); - match event.0.phase { - TouchPhase::Started => { - //println!("touch started: finger {}", event.0.id); - touch_state.fingers.insert(event.0.id, Finger { - id: event.0.id, - device_id: event.0.device_id, - start_position: position, - current_position: position, - prev_position: position, - has_moved: false - }); - }, - TouchPhase::Moved => { - if let Some(finger) = touch_state.fingers.get_mut(&event.0.id) { - finger.has_moved = true; - finger.current_position = position; - } - }, - TouchPhase::Ended | TouchPhase::Cancelled => { - //println!("touch ended: finger {}", event.0.id); - touch_state.fingers.remove(&event.0.id); - }, - } - } -} - -fn process_gilrs_events( - mut gilrs: NonSendSync>, - mut active_gamepad: UniqueViewMut -) { - if let Some(gilrs) = &mut gilrs.0 { - while let Some(Event { id, event: _, time: _ }) = gilrs.next_event() { - active_gamepad.0 = Some(id); - } - } -} - -fn input_start( - mut inputs: UniqueViewMut, - mut prev_inputs: UniqueViewMut, -) { - prev_inputs.0 = *inputs; - *inputs = Inputs::default(); -} - -fn update_input_state ( - raw_inputs: UniqueView, - mut inputs: UniqueViewMut, -) { - inputs.movement += Vec2::new( - raw_inputs.keyboard_state.contains(KeyCode::KeyD as u32) as u32 as f32 - - raw_inputs.keyboard_state.contains(KeyCode::KeyA as u32) as u32 as f32, - raw_inputs.keyboard_state.contains(KeyCode::KeyW as u32) as u32 as f32 - - raw_inputs.keyboard_state.contains(KeyCode::KeyS as u32) as u32 as f32 - ); - inputs.look += raw_inputs.mouse_delta.as_vec2(); - inputs.action_a |= raw_inputs.button_state[0]; - inputs.action_b |= raw_inputs.button_state[1]; -} - -fn update_input_state_gamepad ( - gilrs: NonSendSync>, - active_gamepad: UniqueView, - mut inputs: UniqueViewMut, -) { - if let Some(gilrs) = &gilrs.0 { - if let Some(gamepad) = active_gamepad.0.map(|id| gilrs.gamepad(id)) { - let left_stick = vec2(gamepad.value(Axis::LeftStickX), gamepad.value(Axis::LeftStickY)); - let right_stick = vec2(gamepad.value(Axis::RightStickX), -gamepad.value(Axis::RightStickY)); - inputs.movement += left_stick; - inputs.look += right_stick; - inputs.action_a |= gamepad.is_pressed(Button::South); - inputs.action_b |= gamepad.is_pressed(Button::East); - } - } -} - -fn update_input_state_touch ( - touch_state: UniqueView, - win_size: UniqueView, - mut inputs: UniqueViewMut, -) { - let w = win_size.0.as_dvec2(); - - //Movement - if let Some(finger) = touch_state.query_area( - dvec2(0., 0.), - dvec2(w.x / 2., w.y), - FingerCheck::Start - ).next() { - inputs.movement += (((finger.current_position - finger.start_position) / (w.x / 4.)) * dvec2(1., -1.)).as_vec2(); - } - - //Action buttons - let action_button_fingers = { - let mut action_button_fingers = SetU64::new(); - - //Creates iterator of fingers that started within action button area - let action_finger_iter = || touch_state.query_area( - dvec2(w.x * 0.75, w.y * 0.666), - dvec2(w.x * 0.25, w.y * 0.333), - FingerCheck::Start - ); - - //Action button A - inputs.action_a |= action_finger_iter().filter(|finger| finger.within_area( - dvec2(w.x * (0.75 + 0.125), w.y * 0.666), - dvec2(w.x * 0.125, w.y * 0.333), - FingerCheck::StartOrCurrent - )).map(|x| action_button_fingers.insert(x.id)).next().is_some(); - - //Action button B - inputs.action_b |= action_finger_iter().filter(|finger| finger.within_area( - dvec2(w.x * 0.75, w.y * 0.666), - dvec2(w.x * 0.125, w.y * 0.333), - FingerCheck::StartOrCurrent - )).map(|x| action_button_fingers.insert(x.id)).next().is_some(); - - action_button_fingers - }; - - //Camera controls - if let Some(finger) = touch_state.query_area( - dvec2(w.x / 2., 0.), - dvec2(w.x / 2., w.y), - FingerCheck::Start - ).find(|x| !action_button_fingers.contains(x.id)) { - inputs.look += (((finger.current_position - finger.prev_position) / (w.x / 4.)) * 300.).as_vec2(); - } -} - -fn input_end( - mut inputs: UniqueViewMut, -) { - if inputs.movement.length() >= 1. { - inputs.movement = inputs.movement.normalize(); - } -} - -pub fn init_input ( - storages: AllStoragesView -) { - storages.add_unique_non_send_sync(GilrsWrapper( - Gilrs::new().map_err(|x| { - log::error!("Failed to initialize Gilrs"); - x - }).ok() - )); - storages.add_unique(ActiveGamepad::default()); - storages.add_unique(Inputs::default()); - storages.add_unique(PrevInputs::default()); - storages.add_unique(RawKbmInputState::default()); - storages.add_unique(RawTouchState::default()); -} - -pub fn process_inputs() -> Workload { - ( - process_events, - process_touch_events, - process_gilrs_events, - input_start, - update_input_state, - update_input_state_touch, - update_input_state_gamepad, - input_end, - ).into_sequential_workload() -} +use gilrs::{Gilrs, GamepadId, Button, Event, Axis}; +use glam::{Vec2, DVec2, vec2, dvec2}; +use winit::{ + keyboard::{KeyCode, PhysicalKey}, + event::{DeviceEvent, DeviceId, ElementState, TouchPhase} +}; +use hashbrown::HashMap; +use tinyset::{SetU32, SetU64}; +use nohash_hasher::BuildNoHashHasher; +use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView, NonSendSync}; +use crate::{ + events::{InputDeviceEvent, TouchEvent}, + rendering::WindowSize +}; + +#[derive(Unique, Clone, Copy, Default, Debug)] +pub struct Inputs { + pub movement: Vec2, + pub look: Vec2, + pub action_a: bool, + pub action_b: bool, + pub jump: bool, +} + +#[derive(Unique, Clone, Copy, Default, Debug)] +pub struct PrevInputs(pub Inputs); + +#[derive(Unique, Clone, Default, Debug)] +pub struct RawKbmInputState { + pub keyboard_state: SetU32, + pub button_state: [bool; 32], + pub mouse_delta: DVec2 +} + +#[derive(Clone, Copy, Debug, Default)] +pub enum FingerCheck { + #[default] + Start, + Current, + StartOrCurrent, + StartAndCurrent, + NotMoved, +} + +#[derive(Clone, Copy, Debug)] +pub struct Finger { + pub id: u64, + pub device_id: DeviceId, + pub prev_position: DVec2, + pub start_position: DVec2, + pub current_position: DVec2, + pub has_moved: bool, +} +impl Finger { + pub fn within_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> bool { + let within_area = |pos: DVec2| -> bool { + ((pos - area_pos).min_element() >= 0.) && + ((pos - (area_pos + area_size)).max_element() <= 0.) + }; + let start = within_area(self.start_position); + let current = within_area(self.current_position); + match check { + FingerCheck::Start => start, + FingerCheck::Current => current, + FingerCheck::StartOrCurrent => start || current, + FingerCheck::StartAndCurrent => start && current, + FingerCheck::NotMoved => current && !self.has_moved, + } + } +} + +#[derive(Unique, Clone, Default, Debug)] +pub struct RawTouchState { + //TODO: handle multiple touch devices somehow + pub fingers: HashMap> +} + +impl RawTouchState { + pub fn query_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> impl Iterator + '_ { + self.fingers.iter().filter_map(move |(_, &finger)| { + finger.within_area(area_pos, area_size, check).then_some(finger) + }) + } +} + +#[derive(Unique)] +pub struct GilrsWrapper(Option); + +#[derive(Unique, Default, Clone, Copy)] +pub struct ActiveGamepad(Option); + +//maybe we should manage gamepad state ourselves just like keyboard? +//at least for the sake of consitency + +fn process_events( + device_events: View, + mut input_state: UniqueViewMut, +) { + input_state.mouse_delta = DVec2::ZERO; + for event in device_events.iter() { + match &event.event { + DeviceEvent::MouseMotion { delta } => { + input_state.mouse_delta = DVec2::from(*delta); + }, + DeviceEvent::Key(input) => { + if let PhysicalKey::Code(code) = input.physical_key { + match input.state { + ElementState::Pressed => input_state.keyboard_state.insert(code as u32), + ElementState::Released => input_state.keyboard_state.remove(code as u32), + }; + } + }, + DeviceEvent::Button { button, state } => { + if *button < 32 { + input_state.button_state[*button as usize] = matches!(*state, ElementState::Pressed); + } + }, + _ => () + } + } +} + +fn process_touch_events( + touch_events: View, + mut touch_state: UniqueViewMut, +) { + for (_, finger) in &mut touch_state.fingers { + finger.prev_position = finger.current_position; + } + for event in touch_events.iter() { + let position = dvec2(event.0.location.x, event.0.location.y); + match event.0.phase { + TouchPhase::Started => { + //println!("touch started: finger {}", event.0.id); + touch_state.fingers.insert(event.0.id, Finger { + id: event.0.id, + device_id: event.0.device_id, + start_position: position, + current_position: position, + prev_position: position, + has_moved: false + }); + }, + TouchPhase::Moved => { + if let Some(finger) = touch_state.fingers.get_mut(&event.0.id) { + finger.has_moved = true; + finger.current_position = position; + } + }, + TouchPhase::Ended | TouchPhase::Cancelled => { + //println!("touch ended: finger {}", event.0.id); + touch_state.fingers.remove(&event.0.id); + }, + } + } +} + +fn process_gilrs_events( + mut gilrs: NonSendSync>, + mut active_gamepad: UniqueViewMut +) { + if let Some(gilrs) = &mut gilrs.0 { + while let Some(Event { id, event: _, time: _ }) = gilrs.next_event() { + active_gamepad.0 = Some(id); + } + } +} + +fn input_start( + mut inputs: UniqueViewMut, + mut prev_inputs: UniqueViewMut, +) { + prev_inputs.0 = *inputs; + *inputs = Inputs::default(); +} + +fn update_input_state ( + raw_inputs: UniqueView, + mut inputs: UniqueViewMut, +) { + inputs.movement += Vec2::new( + raw_inputs.keyboard_state.contains(KeyCode::KeyD as u32) as u32 as f32 - + raw_inputs.keyboard_state.contains(KeyCode::KeyA as u32) as u32 as f32, + raw_inputs.keyboard_state.contains(KeyCode::KeyW as u32) as u32 as f32 - + raw_inputs.keyboard_state.contains(KeyCode::KeyS as u32) as u32 as f32 + ); + inputs.look += raw_inputs.mouse_delta.as_vec2(); + inputs.action_a |= raw_inputs.button_state[0]; + inputs.action_b |= raw_inputs.button_state[1]; + inputs.jump |= raw_inputs.keyboard_state.contains(KeyCode::Space as u32); +} + +fn update_input_state_gamepad ( + gilrs: NonSendSync>, + active_gamepad: UniqueView, + mut inputs: UniqueViewMut, +) { + if let Some(gilrs) = &gilrs.0 { + if let Some(gamepad) = active_gamepad.0.map(|id| gilrs.gamepad(id)) { + let left_stick = vec2(gamepad.value(Axis::LeftStickX), gamepad.value(Axis::LeftStickY)); + let right_stick = vec2(gamepad.value(Axis::RightStickX), -gamepad.value(Axis::RightStickY)); + inputs.movement += left_stick; + //HACK: for now, we multiply look by 2 to make it feel more responsive + inputs.look += right_stick * 2.; + inputs.action_a |= gamepad.is_pressed(Button::West); + inputs.action_b |= gamepad.is_pressed(Button::East); + inputs.jump |= gamepad.is_pressed(Button::South); + } + } +} + +fn update_input_state_touch ( + touch_state: UniqueView, + win_size: UniqueView, + mut inputs: UniqueViewMut, +) { + let w = win_size.0.as_dvec2(); + + //Movement + if let Some(finger) = touch_state.query_area( + dvec2(0., 0.), + dvec2(w.x / 2., w.y), + FingerCheck::Start + ).next() { + inputs.movement += (((finger.current_position - finger.start_position) / (w.x / 4.)) * dvec2(1., -1.)).as_vec2(); + } + + //Action buttons + let action_button_fingers = { + let mut action_button_fingers = SetU64::new(); + + //Creates iterator of fingers that started within action button area + let action_finger_iter = || touch_state.query_area( + dvec2(w.x * 0.75, w.y * 0.666), + dvec2(w.x * 0.25, w.y * 0.333), + FingerCheck::Start + ); + + //Action button A + inputs.action_a |= action_finger_iter().filter(|finger| finger.within_area( + dvec2(w.x * (0.75 + 0.125), w.y * 0.666), + dvec2(w.x * 0.125, w.y * 0.333), + FingerCheck::StartOrCurrent + )).map(|x| action_button_fingers.insert(x.id)).next().is_some(); + + //Action button B + inputs.action_b |= action_finger_iter().filter(|finger| finger.within_area( + dvec2(w.x * 0.75, w.y * 0.666), + dvec2(w.x * 0.125, w.y * 0.333), + FingerCheck::StartOrCurrent + )).map(|x| action_button_fingers.insert(x.id)).next().is_some(); + + action_button_fingers + }; + + //Camera controls + if let Some(finger) = touch_state.query_area( + dvec2(w.x / 2., 0.), + dvec2(w.x / 2., w.y), + FingerCheck::Start + ).find(|x| !action_button_fingers.contains(x.id)) { + inputs.look += (((finger.current_position - finger.prev_position) / (w.x / 4.)) * 300.).as_vec2(); + } +} + +fn input_end( + mut inputs: UniqueViewMut, +) { + if inputs.movement.length() >= 1. { + inputs.movement = inputs.movement.normalize(); + } +} + +pub fn init_input ( + storages: AllStoragesView +) { + storages.add_unique_non_send_sync(GilrsWrapper( + Gilrs::new().map_err(|x| { + log::error!("Failed to initialize Gilrs"); + x + }).ok() + )); + storages.add_unique(ActiveGamepad::default()); + storages.add_unique(Inputs::default()); + storages.add_unique(PrevInputs::default()); + storages.add_unique(RawKbmInputState::default()); + storages.add_unique(RawTouchState::default()); +} + +pub fn process_inputs() -> Workload { + ( + process_events, + process_touch_events, + process_gilrs_events, + input_start, + update_input_state, + update_input_state_touch, + update_input_state_gamepad, + input_end, + ).into_sequential_workload() +} diff --git a/kubi/src/lib.rs b/kubi/src/lib.rs index 0371b3e..9fc7b42 100644 --- a/kubi/src/lib.rs +++ b/kubi/src/lib.rs @@ -23,7 +23,7 @@ pub(crate) mod settings; pub(crate) mod camera; pub(crate) mod events; pub(crate) mod input; -pub(crate) mod fly_controller; +pub(crate) mod player_controller; pub(crate) mod block_placement; pub(crate) mod delta_time; pub(crate) mod cursor_lock; @@ -57,7 +57,7 @@ use events::{ player_actions::generate_move_events, }; use input::{init_input, process_inputs}; -use fly_controller::update_controllers; +use player_controller::{debug_switch_ctl_type, update_player_controllers}; use rendering::{ Renderer, RenderTarget, @@ -133,7 +133,8 @@ fn update() -> Workload { update_loaded_world_around_player, ).into_sequential_workload().run_if(is_ingame_or_loading), ( - update_controllers, + debug_switch_ctl_type, + update_player_controllers, update_client_physics_late, generate_move_events, update_raycasts, diff --git a/kubi/src/networking/player.rs b/kubi/src/networking/player.rs index 8b0b470..a70f254 100644 --- a/kubi/src/networking/player.rs +++ b/kubi/src/networking/player.rs @@ -1,101 +1,101 @@ -use glam::{Vec3, Mat4}; -use shipyard::{UniqueViewMut, View, IntoIter, AllStoragesView, AllStoragesViewMut, UniqueView, ViewMut, Get}; -use uflow::{SendMode, client::Event as ClientEvent}; -use kubi_shared::{ - transform::Transform, - networking::{ - messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType}, - channels::Channel, - client::ClientIdMap, - }, -}; -use crate::{ - events::player_actions::PlayerActionEvent, - player::spawn_remote_player_multiplayer, -}; -use super::{UdpClient, NetworkEvent}; - -pub fn init_client_map( - storages: AllStoragesView, -) { - storages.add_unique(ClientIdMap::new()); -} - -pub fn send_player_movement_events( - actions: View, - mut client: UniqueViewMut, -) { - for event in actions.iter() { - let PlayerActionEvent::PositionChanged { position, direction } = event else { - continue - }; - client.0.send( - postcard::to_allocvec(&ClientToServerMessage::PositionChanged { - position: *position, - velocity: Vec3::ZERO, - direction: *direction - }).unwrap().into_boxed_slice(), - Channel::Move as usize, - SendMode::TimeSensitive - ); - } -} - -pub fn receive_player_movement_events( - mut transforms: ViewMut, - network_events: View, - id_map: UniqueView -) { - for event in network_events.iter() { - let ClientEvent::Receive(data) = &event.0 else { - continue - }; - - if !event.is_message_of_type::<{ServerToClientMessageType::PlayerPositionChanged as u8}>() { - continue - } - - let Ok(parsed_message) = postcard::from_bytes(data) else { - log::error!("Malformed message"); - continue - }; - - let ServerToClientMessage::PlayerPositionChanged { - client_id, position, direction - } = parsed_message else { unreachable!() }; - - let Some(&ent_id) = id_map.0.get(&client_id) else { - log::error!("Not in client-id map"); - continue - }; - - let mut transform = (&mut transforms).get(ent_id) - .expect("invalid player entity id"); - - transform.0 = Mat4::from_rotation_translation(direction, position); - } -} - -pub fn receive_player_connect_events( - mut storages: AllStoragesViewMut, -) { - let messages: Vec = storages.borrow::>().unwrap().iter().filter_map(|event| { - let ClientEvent::Receive(data) = &event.0 else { - return None - }; - if !event.is_message_of_type::<{ServerToClientMessageType::PlayerConnected as u8}>() { - return None - }; - let Ok(parsed_message) = postcard::from_bytes(data) else { - log::error!("Malformed message"); - return None - }; - Some(parsed_message) - }).collect(); - - for message in messages { - let ServerToClientMessage::PlayerConnected { init } = message else { unreachable!() }; - log::info!("player connected: {} (id {})", init.username, init.client_id); - spawn_remote_player_multiplayer(&mut storages, init); - } -} +use glam::{Vec3, Mat4}; +use shipyard::{UniqueViewMut, View, IntoIter, AllStoragesView, AllStoragesViewMut, UniqueView, ViewMut, Get}; +use uflow::{SendMode, client::Event as ClientEvent}; +use kubi_shared::{ + transform::Transform, + networking::{ + messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType}, + channels::Channel, + client::ClientIdMap, + }, +}; +use crate::{ + events::player_actions::PlayerActionEvent, + player::spawn_remote_player_multiplayer, +}; +use super::{UdpClient, NetworkEvent}; + +pub fn init_client_map( + storages: AllStoragesView, +) { + storages.add_unique(ClientIdMap::new()); +} + +pub fn send_player_movement_events( + actions: View, + mut client: UniqueViewMut, +) { + for event in actions.iter() { + let PlayerActionEvent::PositionChanged { position, velocity, direction } = event else { + continue + }; + client.0.send( + postcard::to_allocvec(&ClientToServerMessage::PositionChanged { + position: *position, + velocity: *velocity, + direction: *direction + }).unwrap().into_boxed_slice(), + Channel::Move as usize, + SendMode::TimeSensitive + ); + } +} + +pub fn receive_player_movement_events( + mut transforms: ViewMut, + network_events: View, + id_map: UniqueView +) { + for event in network_events.iter() { + let ClientEvent::Receive(data) = &event.0 else { + continue + }; + + if !event.is_message_of_type::<{ServerToClientMessageType::PlayerPositionChanged as u8}>() { + continue + } + + let Ok(parsed_message) = postcard::from_bytes(data) else { + log::error!("Malformed message"); + continue + }; + + let ServerToClientMessage::PlayerPositionChanged { + client_id, position, direction + } = parsed_message else { unreachable!() }; + + let Some(&ent_id) = id_map.0.get(&client_id) else { + log::error!("Not in client-id map"); + continue + }; + + let mut transform = (&mut transforms).get(ent_id) + .expect("invalid player entity id"); + + transform.0 = Mat4::from_rotation_translation(direction, position); + } +} + +pub fn receive_player_connect_events( + mut storages: AllStoragesViewMut, +) { + let messages: Vec = storages.borrow::>().unwrap().iter().filter_map(|event| { + let ClientEvent::Receive(data) = &event.0 else { + return None + }; + if !event.is_message_of_type::<{ServerToClientMessageType::PlayerConnected as u8}>() { + return None + }; + let Ok(parsed_message) = postcard::from_bytes(data) else { + log::error!("Malformed message"); + return None + }; + Some(parsed_message) + }).collect(); + + for message in messages { + let ServerToClientMessage::PlayerConnected { init } = message else { unreachable!() }; + log::info!("player connected: {} (id {})", init.username, init.client_id); + spawn_remote_player_multiplayer(&mut storages, init); + } +} diff --git a/kubi/src/player.rs b/kubi/src/player.rs index b500e53..7fda47e 100644 --- a/kubi/src/player.rs +++ b/kubi/src/player.rs @@ -12,7 +12,7 @@ use kubi_shared::{ use crate::{ camera::Camera, client_physics::ClPhysicsActor, - fly_controller::FlyController, + player_controller::PlayerController, transform::Transform, world::raycast::LookingAtBlock }; @@ -31,7 +31,7 @@ pub fn spawn_player ( Health::new(PLAYER_HEALTH), Transform::default(), Camera::default(), - FlyController, + PlayerController::DEFAULT_FPS_CTL, LookingAtBlock::default(), PlayerHolding(Some(Block::Cobblestone)), Username("LocalPlayer".into()), @@ -53,7 +53,7 @@ pub fn spawn_local_player_multiplayer ( init.health, Transform(Mat4::from_rotation_translation(init.direction, init.position)), Camera::default(), - FlyController, + PlayerController::DEFAULT_FPS_CTL, LookingAtBlock::default(), PlayerHolding::default(), ),( diff --git a/kubi/src/player_controller.rs b/kubi/src/player_controller.rs new file mode 100644 index 0000000..bd0fbc7 --- /dev/null +++ b/kubi/src/player_controller.rs @@ -0,0 +1,130 @@ +use glam::{vec3, EulerRot, Mat4, Quat, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles}; +use shipyard::{track, Component, Get, IntoIter, IntoWithId, IntoWorkload, Unique, UniqueView, View, ViewMut, Workload}; +use winit::keyboard::KeyCode; +use std::f32::consts::PI; +use crate::{client_physics::ClPhysicsActor, delta_time::DeltaTime, input::{Inputs, PrevInputs, RawKbmInputState}, settings::GameSettings, transform::Transform}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PlayerControllerType { + FlyCam, + FpsCtl, +} + +#[derive(Component)] +pub struct PlayerController { + pub control_type: PlayerControllerType, + pub speed: f32, +} + +impl PlayerController { + pub const DEFAULT_FLY_CAM: Self = Self { + control_type: PlayerControllerType::FlyCam, + speed: 50., + }; + + pub const DEFAULT_FPS_CTL: Self = Self { + control_type: PlayerControllerType::FpsCtl, + speed: 10., + }; +} + +pub fn update_player_controllers() -> Workload { + ( + update_look, + update_movement + ).into_sequential_workload() +} + +const MAX_PITCH: f32 = PI/2. - 0.05; + +fn update_look( + controllers: View, + mut transforms: ViewMut, + inputs: UniqueView, + settings: UniqueView, + dt: UniqueView, +) { + let look = inputs.look * settings.mouse_sensitivity * dt.0.as_secs_f32(); + if look == Vec2::ZERO { return } + for (_, mut transform) in (&controllers, &mut transforms).iter() { + let (scale, mut rotation, translation) = transform.0.to_scale_rotation_translation(); + let (mut yaw, mut pitch, _roll) = rotation.to_euler(EulerRot::YXZ); + yaw -= look.x; + pitch -= look.y; + pitch = pitch.clamp(-MAX_PITCH, MAX_PITCH); + rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.).normalize(); + transform.0 = Mat4::from_scale_rotation_translation(scale, rotation, translation); + } +} + +fn update_movement( + controllers: View, + mut transforms: ViewMut, + mut actors: ViewMut, + inputs: UniqueView, + prev_inputs: UniqueView, + dt: UniqueView, +) { + let jump = inputs.jump && !prev_inputs.0.jump; + if (inputs.movement == Vec2::ZERO) && !jump { return } + let movement = inputs.movement.extend(jump as u32 as f32).xzy(); + for (id, (ctl, mut transform)) in (&controllers, &mut transforms).iter().with_id() { + let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation(); + let rotation_norm = rotation.normalize(); + match ctl.control_type { + PlayerControllerType::FlyCam => { + translation += (rotation_norm * Vec3::NEG_Z).normalize() * movement.z * ctl.speed * dt.0.as_secs_f32(); + translation += (rotation_norm * Vec3::X).normalize() * movement.x * ctl.speed * dt.0.as_secs_f32(); + translation += Vec3::Y * movement.y * ctl.speed * dt.0.as_secs_f32(); + transform.0 = Mat4::from_scale_rotation_translation(scale, rotation_norm, translation); + }, + PlayerControllerType::FpsCtl => { + let mut actor = (&mut actors).get(id).unwrap(); + let actor_on_ground = actor.on_ground(); + + let euler = rotation_norm.to_euler(EulerRot::YZX); + let right = Vec2::from_angle(-euler.0).extend(0.).xzy(); + let forward = Vec2::from_angle(-(euler.0 + PI/2.)).extend(0.).xzy(); + + //TODO: remove hardcoded jump force + // actor.apply_constant_force(ctl.speed * ( + // (forward * movement.z) + + // (right * movement.x) + // )); + actor.apply_force( + ctl.speed * ( + (forward * movement.z) + + (right * movement.x) + ) + + Vec3::Y * movement.y * 1250. * (actor_on_ground as u8 as f32) + ); + + // actor.decel = + // (right * (1. - inputs.movement.x.abs()) * 10.) + + // (forward * (1. - inputs.movement.y.abs()) * 10.); + + // translation += forward * movement.z * ctl.speed * dt.0.as_secs_f32(); + // translation += right * movement.x * ctl.speed * dt.0.as_secs_f32(); + // translation += Vec3::Y * movement.y * ctl.speed * dt.0.as_secs_f32(); + + // transform.0 = Mat4::from_scale_rotation_translation(scale, rotation_norm, translation); + } + } + } +} + +pub fn debug_switch_ctl_type( + mut controllers: ViewMut, + mut actors: ViewMut, + kbm_state: UniqueView, +) { + for (mut controller, mut actor) in (&mut controllers, &mut actors).iter() { + if kbm_state.keyboard_state.contains(KeyCode::F4 as u32) { + *controller = PlayerController::DEFAULT_FPS_CTL; + actor.disable = false; + } else if kbm_state.keyboard_state.contains(KeyCode::F5 as u32) { + *controller = PlayerController::DEFAULT_FLY_CAM; + actor.disable = true; + } + } +}