feat: Gravity and underbaked flow implementation
This commit is contained in:
parent
120db729bb
commit
4494b62342
6 changed files with 120 additions and 46 deletions
|
|
@ -1,12 +1,14 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use bevy::math::Vec3;
|
||||||
|
|
||||||
use crate::MAX_FLUID_AMOUNT;
|
use crate::MAX_FLUID_AMOUNT;
|
||||||
|
|
||||||
#[derive(Default,Clone,Copy, PartialEq, Eq)]
|
#[derive(Default,Clone,Copy, PartialEq )]
|
||||||
pub enum GridElement{
|
pub enum GridElement{
|
||||||
#[default]
|
#[default]
|
||||||
Air,
|
Air,
|
||||||
Water{amount: u32},
|
Water{amount: u32, force: Vec3},
|
||||||
Solid,
|
Solid,
|
||||||
Drain,
|
Drain,
|
||||||
Source{source_amount: u32},
|
Source{source_amount: u32},
|
||||||
|
|
@ -16,7 +18,7 @@ impl GridElement {
|
||||||
pub fn can_fill(&self) -> bool {
|
pub fn can_fill(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
GridElement::Air => true,
|
GridElement::Air => true,
|
||||||
GridElement::Water { amount: _ } => true,
|
GridElement::Water { amount: _, force: _ } => true,
|
||||||
GridElement::Solid => false,
|
GridElement::Solid => false,
|
||||||
GridElement::Drain => true,
|
GridElement::Drain => true,
|
||||||
GridElement::Source { source_amount: _ } => false,
|
GridElement::Source { source_amount: _ } => false,
|
||||||
|
|
@ -24,7 +26,7 @@ impl GridElement {
|
||||||
}
|
}
|
||||||
pub fn try_fill(&self, fill_amount: u32) -> Option<u32> {
|
pub fn try_fill(&self, fill_amount: u32) -> Option<u32> {
|
||||||
return match self {
|
return match self {
|
||||||
GridElement::Water { amount } => {
|
GridElement::Water { amount, force: _ } => {
|
||||||
if *amount == MAX_FLUID_AMOUNT {
|
if *amount == MAX_FLUID_AMOUNT {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -40,13 +42,23 @@ impl GridElement {
|
||||||
_ => None,
|
_ => 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 {
|
impl Display for GridElement {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f,"{}",match self {
|
write!(f,"{}",match self {
|
||||||
GridElement::Air => "Air",
|
GridElement::Air => "Air",
|
||||||
GridElement::Water { amount: _ } => "Water",
|
GridElement::Water { amount: _, force: _ } => "Water",
|
||||||
GridElement::Solid => "Solid",
|
GridElement::Solid => "Solid",
|
||||||
GridElement::Drain => "Drain",
|
GridElement::Drain => "Drain",
|
||||||
GridElement::Source { source_amount: _ } => "Source",
|
GridElement::Source { source_amount: _ } => "Source",
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ impl Grid {
|
||||||
index / (self.size.x*self.size.y)
|
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;
|
let bounds = self.cell_amount() as usize;
|
||||||
if from >= bounds || to >= bounds {
|
if from >= bounds || to >= bounds {
|
||||||
error!("Trying to transfer fluid out of bounds!");
|
error!("Trying to transfer fluid out of bounds!");
|
||||||
|
|
@ -53,13 +53,13 @@ impl Grid {
|
||||||
let to_new_value;
|
let to_new_value;
|
||||||
|
|
||||||
match (self.data[to],self.data[from]) {
|
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 moved_amount = water_amount.min(transfer_amount);
|
||||||
let remaining_amount = water_amount-moved_amount;
|
let remaining_amount = water_amount-moved_amount;
|
||||||
to_new_value = Some(GridElement::Water { amount: moved_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 })};
|
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 transfer_amount = from_amount.min(transfer_amount);
|
||||||
let delta = MAX_FLUID_AMOUNT - to_amount;
|
let delta = MAX_FLUID_AMOUNT - to_amount;
|
||||||
if delta < transfer_amount {
|
if delta < transfer_amount {
|
||||||
|
|
@ -70,13 +70,14 @@ impl Grid {
|
||||||
to_amount += transfer_amount;
|
to_amount += transfer_amount;
|
||||||
from_amount -= transfer_amount;
|
from_amount -= transfer_amount;
|
||||||
}
|
}
|
||||||
to_new_value = Some(GridElement::Water { amount: to_amount });
|
let resulting_force = to_force.midpoint(from_force);
|
||||||
from_new_value = if from_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: from_amount })};
|
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 transfer_amount = from_amount.min(transfer_amount);
|
||||||
let new_amount = from_amount - 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;
|
to_new_value = None;
|
||||||
},
|
},
|
||||||
_ => {return;},
|
_ => {return;},
|
||||||
|
|
@ -96,14 +97,14 @@ impl Grid {
|
||||||
let return_amount;
|
let return_amount;
|
||||||
match self.data[at] {
|
match self.data[at] {
|
||||||
GridElement::Air => {
|
GridElement::Air => {
|
||||||
transformed_value = GridElement::Water { amount };
|
transformed_value = GridElement::Water { amount, force: Vec3::ZERO };
|
||||||
return_amount = 0;
|
return_amount = 0;
|
||||||
},
|
},
|
||||||
GridElement::Water { amount: water_amount } => {
|
GridElement::Water { amount: water_amount, force } => {
|
||||||
let diff = MAX_FLUID_AMOUNT - water_amount;
|
let diff = MAX_FLUID_AMOUNT - water_amount;
|
||||||
let fill_amount = diff.min(amount);
|
let fill_amount = diff.min(amount);
|
||||||
return_amount = amount - fill_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; },
|
GridElement::Drain => { return 0; },
|
||||||
_ => { return amount; }
|
_ => { return amount; }
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use bevy::{prelude::*,math::UVec3};
|
use bevy::{prelude::*,math::UVec3};
|
||||||
use crate::{MAX_VERTICAL_FLOW,MAX_HORIZONTAL_FLOW,grid::{Grid,GridElement}};
|
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) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_state::<SimulationStatus>()
|
app.init_state::<SimulationStatus>()
|
||||||
.insert_resource(Time::<Fixed>::from_hz(32.))
|
.insert_resource(Time::<Fixed>::from_hz(32.))
|
||||||
|
.init_resource::<SimulationOptions>()
|
||||||
.add_systems(FixedUpdate, tick.run_if(in_state(SimulationStatus::Continued)));
|
.add_systems(FixedUpdate, tick.run_if(in_state(SimulationStatus::Continued)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,6 +19,7 @@ enum GridAction{
|
||||||
from: UVec3,
|
from: UVec3,
|
||||||
to: UVec3,
|
to: UVec3,
|
||||||
amount: u32,
|
amount: u32,
|
||||||
|
with_force: Vec3,
|
||||||
},
|
},
|
||||||
SpawnLiquid {
|
SpawnLiquid {
|
||||||
at: UVec3,
|
at: UVec3,
|
||||||
|
|
@ -30,7 +34,20 @@ pub enum SimulationStatus {
|
||||||
Continued,
|
Continued,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(mut grid: ResMut<Grid>) {
|
#[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<Grid>, options: Res<SimulationOptions>) {
|
||||||
let mut stack: Vec<GridAction> = Vec::new();
|
let mut stack: Vec<GridAction> = Vec::new();
|
||||||
|
|
||||||
for x in 0..grid.size.x {
|
for x in 0..grid.size.x {
|
||||||
|
|
@ -42,33 +59,44 @@ pub fn tick(mut grid: ResMut<Grid>) {
|
||||||
};
|
};
|
||||||
|
|
||||||
match grid.data[index as usize] {
|
match grid.data[index as usize] {
|
||||||
GridElement::Water { amount } => {
|
GridElement::Water { amount, force } => {
|
||||||
let mut transfer_amount = amount.clone();
|
let mut transfer_amount = amount.clone();
|
||||||
|
let resulting_force = (options.gravity * (amount as f32)).midpoint(force).normalize();
|
||||||
if let Some(gravity_vector) = vector.checked_sub(UVec3::new(0,1,0)) {
|
|
||||||
if let Some(gravity_amount) = grid.data
|
let main_vector = vector.as_ivec3() + resulting_force.round().as_ivec3();
|
||||||
[grid.indexify(gravity_vector).unwrap()]
|
if main_vector.cmplt(IVec3::ZERO).any() == false && main_vector.cmpgt(grid.size.as_ivec3() - IVec3::ONE).any() == false {
|
||||||
.try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) {
|
let index = grid.indexify(main_vector.as_uvec3()).unwrap();
|
||||||
stack.push(GridAction::MoveLiquid { from: vector, to: gravity_vector, amount: gravity_amount });
|
if let Some(main_amount) = grid.data[index].try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) {
|
||||||
transfer_amount -= gravity_amount;
|
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;
|
transfer_amount = transfer_amount.min(MAX_HORIZONTAL_FLOW) / 4;
|
||||||
if transfer_amount > 0 {
|
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)] {
|
for lookup_vector in [
|
||||||
let push_vector = (vector.as_ivec3() + lookup_vector).max(IVec3::ZERO).as_uvec3();
|
resulting_force.rotate_x(PI/2.).round().as_ivec3(),
|
||||||
if push_vector == vector {
|
resulting_force.rotate_z(PI/2.).round().as_ivec3(),
|
||||||
continue;
|
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<Grid>) {
|
||||||
|
|
||||||
for action in stack {
|
for action in stack {
|
||||||
match action {
|
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());
|
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 } => {
|
GridAction::SpawnLiquid { at, amount } => {
|
||||||
let at_index = grid.indexify(at).unwrap();
|
let at_index = grid.indexify(at).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ impl<T: Clone> Clone for Confirmation<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, PartialEq, Eq,Resource)]
|
#[derive(Default, Clone, PartialEq, Resource)]
|
||||||
pub struct EditorTool {
|
pub struct EditorTool {
|
||||||
pub block: GridElement,
|
pub block: GridElement,
|
||||||
pub kind: ToolType
|
pub kind: ToolType
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ pub fn update_cubes(query: Query<(&mut Mesh3d,&mut Visibility,&mut MeshMaterial3
|
||||||
GridElement::Air => {
|
GridElement::Air => {
|
||||||
*visibility = Visibility::Hidden;
|
*visibility = Visibility::Hidden;
|
||||||
},
|
},
|
||||||
GridElement::Water { amount } => {
|
GridElement::Water { amount, force: _ } => {
|
||||||
*visibility = Visibility::Visible;
|
*visibility = Visibility::Visible;
|
||||||
material.0 = materials.water.clone();
|
material.0 = materials.water.clone();
|
||||||
let stage = ((amount as f32)/(MAX_FLUID_AMOUNT as f32) * (FLUID_FULLNES_STAGES-1) as f32).ceil() as usize;
|
let stage = ((amount as f32)/(MAX_FLUID_AMOUNT as f32) * (FLUID_FULLNES_STAGES-1) as f32).ceil() as usize;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use bevy::feathers::{FeathersPlugins, containers::*, controls::*, dark_theme::create_dark_theme, display::*, theme::UiTheme};
|
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 bevy::prelude::*;
|
||||||
|
|
||||||
use crate::MAX_FLUID_AMOUNT;
|
use crate::MAX_FLUID_AMOUNT;
|
||||||
use crate::grid::{Grid, GridElement};
|
use crate::grid::{Grid, GridElement};
|
||||||
use crate::simulation::SimulationStatus;
|
use crate::simulation::{SimulationOptions, SimulationStatus};
|
||||||
use crate::view::editor::{EditorTool,ToolType,Confirmation};
|
use crate::view::editor::{EditorTool,ToolType,Confirmation};
|
||||||
|
|
||||||
pub struct UIPlugin;
|
pub struct UIPlugin;
|
||||||
|
|
@ -33,7 +33,7 @@ const MIN_FREQUENCY: u32 = 1;
|
||||||
fn count_water_amount(grid: Res<Grid>, text: Single<&mut Text, With<WaterLabel>>) {
|
fn count_water_amount(grid: Res<Grid>, text: Single<&mut Text, With<WaterLabel>>) {
|
||||||
let mut sum: u32 = 0;
|
let mut sum: u32 = 0;
|
||||||
for i in 0..grid.cell_amount() {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,6 +82,7 @@ fn debug_column() -> impl Scene {
|
||||||
water_amount_label(),
|
water_amount_label(),
|
||||||
pause_button(),
|
pause_button(),
|
||||||
simulation_speed(),
|
simulation_speed(),
|
||||||
|
gravity_control(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +132,7 @@ fn editor_elements_column() -> impl Scene {
|
||||||
element_button(GridElement::Air),
|
element_button(GridElement::Air),
|
||||||
element_button(GridElement::Solid),
|
element_button(GridElement::Solid),
|
||||||
element_button(GridElement::Drain),
|
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 })
|
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<ValueChange<f32>>, mut gravity: ResMut<SimulationOptions>| {
|
||||||
|
gravity.gravity.x = change.value;
|
||||||
|
}),
|
||||||
|
@FeathersNumberInput {
|
||||||
|
@label_text: "Y"
|
||||||
|
}
|
||||||
|
on(|change: On<ValueChange<f32>>, mut gravity: ResMut<SimulationOptions>| {
|
||||||
|
gravity.gravity.y = change.value;
|
||||||
|
}),
|
||||||
|
@FeathersNumberInput {
|
||||||
|
@label_text: "Z"
|
||||||
|
}
|
||||||
|
on(|change: On<ValueChange<f32>>, mut gravity: ResMut<SimulationOptions>| {
|
||||||
|
gravity.gravity.z = change.value;
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue