From 183475c60f5c546dd5293931f85222f137f37f4c Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Sat, 31 Dec 2022 22:47:38 +0200 Subject: [PATCH] wip: tweaking disperse system (random disperse, stabilisation) --- Cargo.lock | 1 + Cargo.toml | 1 + src/game.rs | 11 +- src/game/camera.rs | 11 +- src/game/debug.rs | 2 +- src/game/debug/terrain.rs | 7 +- src/game/player.rs | 3 - src/terrain2d.rs | 156 +++++++++++++++++++---------- src/terrain2d/terrain_gen2d.rs | 1 + src/terrain2d/texel_behaviour2d.rs | 36 ++++++- 10 files changed, 154 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6fde78..141ab5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2084,6 +2084,7 @@ dependencies = [ "bevy-inspector-egui", "bevy_prototype_debug_lines", "bevy_rapier2d", + "fastrand", "lazy_static", "noise", ] diff --git a/Cargo.toml b/Cargo.toml index e609484..7bb096a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ bevy = { version = "0.9.0", features = ["dynamic"] } bevy-inspector-egui = "0.14.0" bevy_prototype_debug_lines = "0.9.0" bevy_rapier2d = "0.19.0" +fastrand = "1.8.0" lazy_static = "1.4.0" noise = "0.8.2" diff --git a/src/game.rs b/src/game.rs index a4d93ff..1fa1671 100644 --- a/src/game.rs +++ b/src/game.rs @@ -7,10 +7,7 @@ use crate::{ }; use self::{ - camera::{GameCameraPlugin, WORLD_WIDTH}, - debug::DebugPlugin, - kinematic::KinematicPlugin, - player::PlayerPlugin, + camera::GameCameraPlugin, debug::DebugPlugin, kinematic::KinematicPlugin, player::PlayerPlugin, }; pub mod camera; @@ -42,8 +39,8 @@ fn setup_window(mut windows: ResMut) { fn setup_terrain(mut commands: Commands, mut terrain: ResMut) { let terrain_gen = TerrainGen2D::new(432678); - for y in 0..(WORLD_WIDTH / Chunk2D::SIZE_Y as i32) { - for x in 0..(WORLD_WIDTH / Chunk2D::SIZE_X as i32) { + for y in 0..(Terrain2D::WORLD_HEIGHT / Chunk2D::SIZE_Y as i32) { + for x in 0..(Terrain2D::WORLD_WIDTH / Chunk2D::SIZE_X as i32) { let position = Vector2I { x, y }; terrain.add_chunk(position, terrain_gen.gen_chunk(&position)); } @@ -60,6 +57,6 @@ fn setup_terrain(mut commands: Commands, mut terrain: ResMut) { .spawn(Name::new("Right wall")) .insert(Collider::halfspace(Vec2::NEG_X).unwrap()) .insert(TransformBundle::from_transform( - Transform::from_translation(Vec3::new(WORLD_WIDTH as f32, 0.0, 0.0)), + Transform::from_translation(Vec3::new(Terrain2D::WORLD_WIDTH as f32, 0.0, 0.0)), )); } diff --git a/src/game/camera.rs b/src/game/camera.rs index 7c4b52b..28ff2cc 100644 --- a/src/game/camera.rs +++ b/src/game/camera.rs @@ -4,9 +4,10 @@ use bevy::{ }; use bevy_inspector_egui::{Inspectable, RegisterInspectable}; -use crate::util::{move_towards_vec3, vec3_lerp}; - -pub const WORLD_WIDTH: i32 = 512; +use crate::{ + terrain2d::Terrain2D, + util::{move_towards_vec3, vec3_lerp}, +}; pub struct GameCameraPlugin; @@ -48,7 +49,7 @@ fn camera_setup(mut commands: Commands) { Name::new("Camera"), Camera2dBundle { projection: OrthographicProjection { - scaling_mode: ScalingMode::FixedHorizontal(WORLD_WIDTH as f32), + scaling_mode: ScalingMode::FixedHorizontal(Terrain2D::WORLD_WIDTH as f32), window_origin: WindowOrigin::Center, scale: 1.0 / 2.0, ..default() @@ -79,7 +80,7 @@ fn camera_system( for (mut camera_transform, projection) in camera_query.iter_mut() { let left_limit = 0.0; - let right_limit = WORLD_WIDTH as f32; + let right_limit = Terrain2D::WORLD_WIDTH as f32; let offset = Vec3::new(0.0, 0.0, 999.9); match follow.movement { FollowMovement::Instant => { diff --git a/src/game/debug.rs b/src/game/debug.rs index 2700666..8c39cb8 100644 --- a/src/game/debug.rs +++ b/src/game/debug.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use bevy_prototype_debug_lines::DebugLinesPlugin; -mod terrain; +pub mod terrain; use terrain::TerrainDebugPlugin; diff --git a/src/game/debug/terrain.rs b/src/game/debug/terrain.rs index 7e00b34..73da944 100644 --- a/src/game/debug/terrain.rs +++ b/src/game/debug/terrain.rs @@ -23,7 +23,10 @@ struct TerrainBrush2D { impl Default for TerrainBrush2D { fn default() -> Self { - TerrainBrush2D { radius: 3, tile: 7 } + TerrainBrush2D { + radius: 40, + tile: 8, + } } } @@ -204,7 +207,7 @@ fn chunk_debugger(terrain: Res, mut debug_draw: ResMut) { } } -fn draw_box(debug_draw: &mut DebugLines, min: Vec3, max: Vec3, color: Color, duration: f32) { +pub fn draw_box(debug_draw: &mut DebugLines, min: Vec3, max: Vec3, color: Color, duration: f32) { let points = vec![ Vec3::new(min.x, min.y, min.z), Vec3::new(max.x, min.y, min.z), diff --git a/src/game/player.rs b/src/game/player.rs index 67540fb..460259e 100644 --- a/src/game/player.rs +++ b/src/game/player.rs @@ -81,9 +81,6 @@ pub fn player_spawn(mut commands: Commands) { }, ..default() }) - .insert(TransformBundle::from_transform(Transform::from_xyz( - 256.0, 128.0, 0.0, - ))) .insert(Collider::cuboid(3.0, 6.0)) .insert(PlayerBundle { kinematic, diff --git a/src/terrain2d.rs b/src/terrain2d.rs index c5be6ec..99629a5 100644 --- a/src/terrain2d.rs +++ b/src/terrain2d.rs @@ -17,10 +17,7 @@ pub use terrain_gen2d::*; pub use texel2d::*; pub use texel_behaviour2d::*; -use crate::{ - game::camera::WORLD_WIDTH, - util::{frame_counter::FrameCounter, math::*, Vector2I}, -}; +use crate::util::{frame_counter::FrameCounter, math::*, Vector2I}; pub struct Terrain2DPlugin; @@ -46,10 +43,10 @@ impl Plugin for Terrain2DPlugin { app.register_type::() .insert_resource(Terrain2D::new( - Some(WORLD_WIDTH * 2), + Some(Terrain2D::WORLD_HEIGHT), Some(0), Some(0), - Some(WORLD_WIDTH), + Some(Terrain2D::WORLD_WIDTH), )) .add_event::() .add_system_to_stage(TerrainStages::Simulation, terrain_simulation) @@ -74,7 +71,11 @@ pub enum TerrainStages { ChunkSync, } -fn terrain_simulation(mut terrain: ResMut, frame_counter: Res) { +fn terrain_simulation( + mut terrain: ResMut, + frame_counter: Res, + mut debug_draw: ResMut, +) { let simulation_frame = (frame_counter.frame % u8::MAX as u64) as u8 + 1; let indices = terrain @@ -152,21 +153,29 @@ fn terrain_simulation(mut terrain: ResMut, frame_counter: Res, terrain: &mut Terrain2D, frame_counter: &FrameCounter, + debug_draw: &mut bevy_prototype_debug_lines::DebugLines, ) { use u32 as Capacity; - let mut total_densities: HashMap = HashMap::new(); - // let mut total_densities: Vec<(TexelID, Capacity)> = vec![]; + use u8 as Min; + use u8 as Max; + let mut total_densities: HashMap = HashMap::new(); let mut valid_globals = vec![]; for global in global_positions.iter() { let (texel, behaviour) = terrain.get_texel_behaviour(global); @@ -176,37 +185,75 @@ fn disperse_gas( match (texel, behaviour) { (Some(texel), Some(behaviour)) => { if behaviour.form == TexelForm::Gas { - total_densities.insert( - texel.id, - texel.density as u32 - + total_densities.get(&texel.id).map_or(0, |density| *density), - ); + if let Some((old_density, old_min, old_max)) = total_densities.get(&texel.id) { + total_densities.insert( + texel.id, + ( + texel.density as u32 + *old_density, + texel.density.min(*old_min), + texel.density.max(*old_max), + ), + ); + } else { + total_densities.insert( + texel.id, + (texel.density as u32, texel.density, texel.density), + ); + } } } (_, _) => (), } } - let mut total_densities: Vec<(TexelID, Capacity)> = - total_densities.iter().map(|(t, d)| (*t, *d)).collect(); + let mut total_densities: Vec<(TexelID, Capacity, Min, Max)> = total_densities + .iter() + .map(|(t, (d, min, max))| (*t, *d, *min, *max)) + .collect(); if total_densities.len() == 0 { return; } - total_densities.sort_unstable_by_key(|(_, density)| *density); + total_densities.sort_unstable_by_key(|(_, density, _, _)| *density); total_densities.reverse(); const TILE_CAPACITY: u32 = u8::MAX as u32; let free_slots = valid_globals.len() as u32 - total_densities .iter() - .map(|(_, v)| (*v / (TILE_CAPACITY + 1)) + 1) + .map(|(_, v, _, _)| (*v / (TILE_CAPACITY + 1)) + 1) .sum::(); + // Stop if the gas is already close to a stable state + const STABLE_TRESHOLD: u8 = 10; + if total_densities.iter().all(|(_, _, min, max)| { + if u8::abs_diff(*min, *max) > STABLE_TRESHOLD { + return false; + } + free_slots > 0 && *max <= STABLE_TRESHOLD + }) { + // // DEBUG: draw box for stabilized area + // let mut min = valid_globals.first().unwrap().clone(); + // let mut max = valid_globals.first().unwrap().clone(); + // for global in valid_globals.iter() { + // min = Vector2I::min(&min, global); + // max = Vector2I::max(&max, global); + // } + // max = max + Vector2I::ONE; + // crate::game::debug::terrain::draw_box( + // debug_draw, + // Vec3::from(min), + // Vec3::from(max), + // Color::CYAN, + // 0.0, + // ); + return; + } + // Allocate slots let mut slots: Vec<(TexelID, u32)> = vec![]; - for (id, density) in total_densities.iter() { + for (id, density, _, _) in total_densities.iter() { let min_slots = (density / (TILE_CAPACITY + 1)) + 1; slots.push((*id, min_slots)); } @@ -217,7 +264,7 @@ fn disperse_gas( // Disperse into given slots let mut texels: Vec = vec![]; - for (id, total_density) in total_densities.iter() { + for (id, total_density, _, _) in total_densities.iter() { let slots = slots.iter().find(|s| s.0 == *id).unwrap().1; let mut density_left = *total_density; for i in 0..slots { @@ -242,6 +289,7 @@ fn disperse_gas( panic!("disperse_gas() - valid_globals is shorter than texels"); } + fastrand::shuffle(&mut valid_globals); for i in 0..valid_globals.len() { let global = valid_globals[i]; if i < texels.len() { @@ -266,35 +314,37 @@ fn simulate_texel(global: Vector2I, terrain: &mut Terrain2D, frame_counter: &Fra let grav_offset = Vector2I::from(gravity); let grav_pos = global + grav_offset; - // Try falling - { - let (_, other_behaviour) = terrain.get_texel_behaviour(&grav_pos); - if TexelBehaviour2D::can_displace(&behaviour, &other_behaviour) { - terrain.swap_texels(&global, &grav_pos, Some(simulation_frame)); - return; + if behaviour.form != TexelForm::Gas || gravity.abs() > fastrand::u8(0..u8::MAX) { + // Try falling + { + let (_, other_behaviour) = terrain.get_texel_behaviour(&grav_pos); + if TexelBehaviour2D::can_displace(&behaviour, &other_behaviour) { + terrain.swap_texels(&global, &grav_pos, Some(simulation_frame)); + return; + } + if terrain.can_transfer_density(&global, &grav_pos) { + terrain.transfer_density(&global, &grav_pos, gravity, Some(simulation_frame)) + } } - if terrain.can_transfer_density(&global, &grav_pos) { - terrain.transfer_density(&global, &grav_pos, gravity, Some(simulation_frame)) - } - } - // Try "sliding" - let mut dirs = vec![Vector2I::RIGHT, Vector2I::LEFT]; - if ((frame_counter.frame / 73) % 2) as i32 == global.y % 2 { - dirs.reverse(); - } - for dir in dirs.iter() { - let slide_pos = match behaviour.form { - TexelForm::Solid => grav_pos + *dir, - TexelForm::Liquid | TexelForm::Gas => global + *dir, - }; - let (_, other_behaviour) = terrain.get_texel_behaviour(&slide_pos); - if TexelBehaviour2D::can_displace(&behaviour, &other_behaviour) { - terrain.swap_texels(&global, &slide_pos, Some(simulation_frame)); - return; + // Try "sliding" + let mut dirs = vec![Vector2I::RIGHT, Vector2I::LEFT]; + if ((frame_counter.frame / 73) % 2) as i32 == global.y % 2 { + dirs.reverse(); } - if terrain.can_transfer_density(&global, &grav_pos) { - terrain.transfer_density(&global, &grav_pos, gravity, Some(simulation_frame)) + for dir in dirs.iter() { + let slide_pos = match behaviour.form { + TexelForm::Solid => grav_pos + *dir, + TexelForm::Liquid | TexelForm::Gas => global + *dir, + }; + let (_, other_behaviour) = terrain.get_texel_behaviour(&slide_pos); + if TexelBehaviour2D::can_displace(&behaviour, &other_behaviour) { + terrain.swap_texels(&global, &slide_pos, Some(simulation_frame)); + return; + } + if terrain.can_transfer_density(&global, &grav_pos) { + terrain.transfer_density(&global, &grav_pos, gravity, Some(simulation_frame)) + } } } } @@ -331,13 +381,16 @@ pub struct Terrain2D { } impl Terrain2D { + pub const WORLD_WIDTH: i32 = 512; + pub const WORLD_HEIGHT: i32 = Self::WORLD_WIDTH * 2; + pub fn new( top_boundary: Option, bottom_boundary: Option, left_boundary: Option, right_boundary: Option, - ) -> Terrain2D { - Terrain2D { + ) -> Self { + Self { chunk_map: HashMap::new(), events: Vec::new(), top_boundary, @@ -518,10 +571,7 @@ impl Terrain2D { ) { let from = self.get_texel(from_global).unwrap_or_default(); let to = self.get_texel(to_global).unwrap_or_default(); - let max_transfer = match gravity { - TexelGravity::Down(grav) => grav, - TexelGravity::Up(grav) => grav, - }; + let max_transfer = gravity.abs(); let transfer = (u8::MAX - to.density).min(max_transfer).min(from.density); if from.density - transfer == 0 { self.set_texel(&from_global, Texel2D::default(), simulation_frame); diff --git a/src/terrain2d/terrain_gen2d.rs b/src/terrain2d/terrain_gen2d.rs index 1fde062..7da660b 100644 --- a/src/terrain2d/terrain_gen2d.rs +++ b/src/terrain2d/terrain_gen2d.rs @@ -1,6 +1,7 @@ use noise::{NoiseFn, PerlinSurflet}; use super::*; +use crate::util::{inverse_lerp, lerp}; pub struct TerrainGen2D { pub seed: u32, diff --git a/src/terrain2d/texel_behaviour2d.rs b/src/terrain2d/texel_behaviour2d.rs index b85182b..e7ddb06 100644 --- a/src/terrain2d/texel_behaviour2d.rs +++ b/src/terrain2d/texel_behaviour2d.rs @@ -70,7 +70,7 @@ lazy_static! { name: Cow::Borrowed("light gas"), color: Color::rgba(0.0, 1.0, 0.0, 0.5), form: TexelForm::Gas, - gravity: Some(TexelGravity::Up(10)), + gravity: Some(TexelGravity::Up(160)), ..default() }, ); @@ -81,7 +81,7 @@ lazy_static! { name: Cow::Borrowed("heavy gas"), color: Color::rgba(1.0, 0.5, 0.5, 0.5), form: TexelForm::Gas, - gravity: Some(TexelGravity::Down(10)), + gravity: Some(TexelGravity::Down(60)), ..default() }, ); @@ -128,9 +128,16 @@ lazy_static! { result }; + static ref FORM_DISPLACEMENT_PRIORITY: HashMap = { + let mut result = HashMap::new(); + result.insert(TexelForm::Gas, 0); + result.insert(TexelForm::Liquid, 1); + result.insert(TexelForm::Solid, 2); + result + }; } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub enum TexelForm { #[default] // Solid materials, when affected by gravity, create pyramid-like piles @@ -141,6 +148,15 @@ pub enum TexelForm { Gas, } +impl TexelForm { + fn priority(&self) -> u8 { + FORM_DISPLACEMENT_PRIORITY + .get(self) + .cloned() + .unwrap_or_default() + } +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum TexelGravity { Down(u8), @@ -156,6 +172,15 @@ impl From for Vector2I { } } +impl TexelGravity { + pub fn abs(&self) -> u8 { + match self { + TexelGravity::Down(grav) => *grav, + TexelGravity::Up(grav) => *grav, + } + } +} + #[derive(Clone, Debug)] pub struct TexelBehaviour2D { pub name: Cow<'static, str>, @@ -206,7 +231,10 @@ impl TexelBehaviour2D { let to = if let Some(to) = to { to } else { return true }; match (from.form, to.form) { - (_, to_form) => { + (from_form, to_form) => { + if from_form.priority() != to_form.priority() { + return from_form.priority() > to_form.priority(); + } if let (Some(from_grav), Some(to_grav)) = (from.gravity, to.gravity) { match (from_grav, to_grav) { (TexelGravity::Down(from_grav), TexelGravity::Down(to_grav)) => {