diff --git a/src/grid/element.rs b/src/grid/element.rs index f5e9d24..c7a0026 100644 --- a/src/grid/element.rs +++ b/src/grid/element.rs @@ -1,12 +1,14 @@ use std::fmt::Display; +use bevy::math::Vec3; + use crate::MAX_FLUID_AMOUNT; -#[derive(Default,Clone,Copy, PartialEq, Eq)] +#[derive(Default,Clone,Copy, PartialEq )] pub enum GridElement{ #[default] Air, - Water{amount: u32}, + Water{amount: u32, force: Vec3}, Solid, Drain, Source{source_amount: u32}, @@ -16,7 +18,7 @@ impl GridElement { pub fn can_fill(&self) -> bool { match self { GridElement::Air => true, - GridElement::Water { amount: _ } => true, + GridElement::Water { amount: _, force: _ } => true, GridElement::Solid => false, GridElement::Drain => true, GridElement::Source { source_amount: _ } => false, @@ -24,7 +26,7 @@ impl GridElement { } pub fn try_fill(&self, fill_amount: u32) -> Option { return match self { - GridElement::Water { amount } => { + GridElement::Water { amount, force: _ } => { if *amount == MAX_FLUID_AMOUNT { return None; } @@ -40,13 +42,23 @@ impl GridElement { _ => None, } } + pub fn loosely_eq(&self,other: &Self) -> bool { + match (self, other) { + (GridElement::Air,GridElement::Air) => true, + (GridElement::Water { amount: _, force: _ }, GridElement::Water { amount: _, force: _ }) => true, + (GridElement::Solid, GridElement::Solid) => true, + (GridElement::Drain, GridElement::Drain) => true, + (GridElement::Source { source_amount: _ }, GridElement::Source { source_amount: _ }) => true, + _ => false + } + } } impl Display for GridElement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f,"{}",match self { GridElement::Air => "Air", - GridElement::Water { amount: _ } => "Water", + GridElement::Water { amount: _, force: _ } => "Water", GridElement::Solid => "Solid", GridElement::Drain => "Drain", GridElement::Source { source_amount: _ } => "Source", diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 5f33e2e..16a8262 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -42,7 +42,7 @@ impl Grid { index / (self.size.x*self.size.y) ); } - pub fn tranfer_fluid(&mut self,from: usize, to: usize, transfer_amount: u32) { + pub fn tranfer_fluid(&mut self,from: usize, to: usize, transfer_amount: u32, from_force: Vec3) { let bounds = self.cell_amount() as usize; if from >= bounds || to >= bounds { error!("Trying to transfer fluid out of bounds!"); @@ -53,13 +53,13 @@ impl Grid { let to_new_value; match (self.data[to],self.data[from]) { - (GridElement::Air,GridElement::Water { amount: water_amount }) => { + (GridElement::Air,GridElement::Water { amount: water_amount, force }) => { let moved_amount = water_amount.min(transfer_amount); let remaining_amount = water_amount-moved_amount; - to_new_value = Some(GridElement::Water { amount: moved_amount }); - from_new_value = if remaining_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: remaining_amount })}; + to_new_value = Some(GridElement::Water { amount: moved_amount, force }); + from_new_value = if remaining_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: remaining_amount, force: from_force })}; }, - (GridElement::Water { amount: mut to_amount }, GridElement::Water { amount: mut from_amount }) => { + (GridElement::Water { amount: mut to_amount, force: to_force }, GridElement::Water { amount: mut from_amount, force: _ }) => { let transfer_amount = from_amount.min(transfer_amount); let delta = MAX_FLUID_AMOUNT - to_amount; if delta < transfer_amount { @@ -70,13 +70,14 @@ impl Grid { to_amount += transfer_amount; from_amount -= transfer_amount; } - to_new_value = Some(GridElement::Water { amount: to_amount }); - from_new_value = if from_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: from_amount })}; + let resulting_force = to_force.midpoint(from_force); + to_new_value = Some(GridElement::Water { amount: to_amount, force: resulting_force }); + from_new_value = if from_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: from_amount, force: resulting_force })}; }, - (GridElement::Drain, GridElement::Water { amount: from_amount }) => { + (GridElement::Drain, GridElement::Water { amount: from_amount, force }) => { let transfer_amount = from_amount.min(transfer_amount); let new_amount = from_amount - transfer_amount; - from_new_value = if new_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: new_amount })}; + from_new_value = if new_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: new_amount, force })}; to_new_value = None; }, _ => {return;}, @@ -96,14 +97,14 @@ impl Grid { let return_amount; match self.data[at] { GridElement::Air => { - transformed_value = GridElement::Water { amount }; + transformed_value = GridElement::Water { amount, force: Vec3::ZERO }; return_amount = 0; }, - GridElement::Water { amount: water_amount } => { + GridElement::Water { amount: water_amount, force } => { let diff = MAX_FLUID_AMOUNT - water_amount; let fill_amount = diff.min(amount); return_amount = amount - fill_amount; - transformed_value = GridElement::Water { amount: water_amount + fill_amount }; + transformed_value = GridElement::Water { amount: water_amount + fill_amount, force }; }, GridElement::Drain => { return 0; }, _ => { return amount; } diff --git a/src/simulation.rs b/src/simulation.rs index 531f544..c96590e 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -1,3 +1,5 @@ +use std::f32::consts::PI; + use bevy::{prelude::*,math::UVec3}; use crate::{MAX_VERTICAL_FLOW,MAX_HORIZONTAL_FLOW,grid::{Grid,GridElement}}; @@ -7,6 +9,7 @@ impl Plugin for SimulationPlugin { fn build(&self, app: &mut App) { app.init_state::() .insert_resource(Time::::from_hz(32.)) + .init_resource::() .add_systems(FixedUpdate, tick.run_if(in_state(SimulationStatus::Continued))); } } @@ -16,6 +19,7 @@ enum GridAction{ from: UVec3, to: UVec3, amount: u32, + with_force: Vec3, }, SpawnLiquid { at: UVec3, @@ -30,7 +34,20 @@ pub enum SimulationStatus { Continued, } -pub fn tick(mut grid: ResMut) { +#[derive(Resource,Clone)] +pub struct SimulationOptions { + pub gravity: Vec3 +} + +impl Default for SimulationOptions { + fn default() -> Self { + Self { + gravity: Vec3::NEG_Y * 9.8, + } + } +} + +pub fn tick(mut grid: ResMut, options: Res) { let mut stack: Vec = Vec::new(); for x in 0..grid.size.x { @@ -42,33 +59,44 @@ pub fn tick(mut grid: ResMut) { }; match grid.data[index as usize] { - GridElement::Water { amount } => { + GridElement::Water { amount, force } => { let mut transfer_amount = amount.clone(); - - if let Some(gravity_vector) = vector.checked_sub(UVec3::new(0,1,0)) { - if let Some(gravity_amount) = grid.data - [grid.indexify(gravity_vector).unwrap()] - .try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) { - stack.push(GridAction::MoveLiquid { from: vector, to: gravity_vector, amount: gravity_amount }); - transfer_amount -= gravity_amount; + let resulting_force = (options.gravity * (amount as f32)).midpoint(force).normalize(); + + let main_vector = vector.as_ivec3() + resulting_force.round().as_ivec3(); + if main_vector.cmplt(IVec3::ZERO).any() == false && main_vector.cmpgt(grid.size.as_ivec3() - IVec3::ONE).any() == false { + let index = grid.indexify(main_vector.as_uvec3()).unwrap(); + if let Some(main_amount) = grid.data[index].try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) { + stack.push(GridAction::MoveLiquid { + from: vector, + to: main_vector.as_uvec3(), + amount: main_amount, + with_force: main_vector.as_vec3() * (main_amount as f32) } + ); + transfer_amount -= main_amount; } } transfer_amount = transfer_amount.min(MAX_HORIZONTAL_FLOW) / 4; if transfer_amount > 0 { - for lookup_vector in [IVec3::new(1,0,0),IVec3::new(0,0,1),IVec3::new(-1,0,0),IVec3::new(0,0,-1)] { - let push_vector = (vector.as_ivec3() + lookup_vector).max(IVec3::ZERO).as_uvec3(); - if push_vector == vector { - continue; + for lookup_vector in [ + resulting_force.rotate_x(PI/2.).round().as_ivec3(), + resulting_force.rotate_z(PI/2.).round().as_ivec3(), + resulting_force.rotate_x(-PI/2.).round().as_ivec3(), + resulting_force.rotate_z(-PI/2.).round().as_ivec3()] { + let side_vector = vector.as_ivec3() + lookup_vector; + if side_vector.cmplt(IVec3::ZERO).any() == false && side_vector.cmpgt(grid.size.as_ivec3() - IVec3::ONE).any() == false { + let index = grid.indexify(side_vector.as_uvec3()).unwrap(); + if let Some(side_amount) = grid.data[index].try_fill(transfer_amount.min(MAX_HORIZONTAL_FLOW)) { + stack.push(GridAction::MoveLiquid { + from: vector, + to: side_vector.as_uvec3(), + amount: side_amount, + with_force: side_vector.as_vec3() * (side_amount as f32) } + ); + transfer_amount -= side_amount; } - let Some(push_index) = grid.indexify(push_vector) else { - continue; - }; - - let Some(push_amount) = grid.data[push_index].try_fill(transfer_amount) else { - continue; - }; - stack.push(GridAction::MoveLiquid { from: vector, to: push_vector, amount: push_amount }); + } } } }, @@ -100,9 +128,9 @@ pub fn tick(mut grid: ResMut) { for action in stack { match action { - GridAction::MoveLiquid { from, to, amount } => { + GridAction::MoveLiquid { from, to, amount, with_force } => { let (from_index, to_index) = (grid.indexify(from).unwrap(), grid.indexify(to).unwrap()); - grid.tranfer_fluid(from_index, to_index, amount); + grid.tranfer_fluid(from_index, to_index, amount, with_force); }, GridAction::SpawnLiquid { at, amount } => { let at_index = grid.indexify(at).unwrap(); diff --git a/src/view/editor.rs b/src/view/editor.rs index ac9fb5d..4658eb6 100644 --- a/src/view/editor.rs +++ b/src/view/editor.rs @@ -49,7 +49,7 @@ impl Clone for Confirmation { } } -#[derive(Default, Clone, PartialEq, Eq,Resource)] +#[derive(Default, Clone, PartialEq, Resource)] pub struct EditorTool { pub block: GridElement, pub kind: ToolType diff --git a/src/view/game.rs b/src/view/game.rs index e275a68..8758c8c 100644 --- a/src/view/game.rs +++ b/src/view/game.rs @@ -134,7 +134,7 @@ pub fn update_cubes(query: Query<(&mut Mesh3d,&mut Visibility,&mut MeshMaterial3 GridElement::Air => { *visibility = Visibility::Hidden; }, - GridElement::Water { amount } => { + GridElement::Water { amount, force: _ } => { *visibility = Visibility::Visible; material.0 = materials.water.clone(); let stage = ((amount as f32)/(MAX_FLUID_AMOUNT as f32) * (FLUID_FULLNES_STAGES-1) as f32).ceil() as usize; diff --git a/src/view/ui.rs b/src/view/ui.rs index 3fb18fe..1f5b140 100644 --- a/src/view/ui.rs +++ b/src/view/ui.rs @@ -1,10 +1,10 @@ use bevy::feathers::{FeathersPlugins, containers::*, controls::*, dark_theme::create_dark_theme, display::*, theme::UiTheme}; -use bevy::ui_widgets::{Activate}; +use bevy::ui_widgets::{Activate, ValueChange}; use bevy::prelude::*; use crate::MAX_FLUID_AMOUNT; use crate::grid::{Grid, GridElement}; -use crate::simulation::SimulationStatus; +use crate::simulation::{SimulationOptions, SimulationStatus}; use crate::view::editor::{EditorTool,ToolType,Confirmation}; pub struct UIPlugin; @@ -33,7 +33,7 @@ const MIN_FREQUENCY: u32 = 1; fn count_water_amount(grid: Res, text: Single<&mut Text, With>) { let mut sum: u32 = 0; for i in 0..grid.cell_amount() { - let GridElement::Water { amount: water_amount } = grid.data[i as usize] else { + let GridElement::Water { amount: water_amount, force: _ } = grid.data[i as usize] else { continue; }; @@ -82,6 +82,7 @@ fn debug_column() -> impl Scene { water_amount_label(), pause_button(), simulation_speed(), + gravity_control(), ] } } @@ -131,7 +132,7 @@ fn editor_elements_column() -> impl Scene { element_button(GridElement::Air), element_button(GridElement::Solid), element_button(GridElement::Drain), - element_button(GridElement::Water { amount: MAX_FLUID_AMOUNT }), + element_button(GridElement::Water { amount: MAX_FLUID_AMOUNT, force: Vec3::ZERO }), element_button(GridElement::Source { source_amount: MAX_FLUID_AMOUNT }) ] } @@ -228,3 +229,35 @@ fn simulation_speed() -> impl Scene { ] } } + +fn gravity_control() -> impl Scene { + bsn! { + Node { + display: Display::Flex, + align_items: AlignItems::Stretch, + justify_content: JustifyContent::Start, + width: percent(100), + height: px(32), + } + Children [ + @FeathersNumberInput { + @label_text: "X" + } + on(|change: On>, mut gravity: ResMut| { + gravity.gravity.x = change.value; + }), + @FeathersNumberInput { + @label_text: "Y" + } + on(|change: On>, mut gravity: ResMut| { + gravity.gravity.y = change.value; + }), + @FeathersNumberInput { + @label_text: "Z" + } + on(|change: On>, mut gravity: ResMut| { + gravity.gravity.z = change.value; + }), + ] + } +}