refactor: split structures and functions into related modules
This commit is contained in:
parent
1119f7b0a0
commit
4109e29d4d
6 changed files with 223 additions and 370 deletions
29
src/grid/element.rs
Normal file
29
src/grid/element.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use crate::MAX_FLUID_AMOUNT;
|
||||||
|
|
||||||
|
#[derive(Default,Clone,Copy)]
|
||||||
|
pub enum GridElement{
|
||||||
|
#[default]
|
||||||
|
Air,
|
||||||
|
Water{amount: u32},
|
||||||
|
Solid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GridElement {
|
||||||
|
pub fn try_fill(&self, fill_amount: u32) -> Option<u32> {
|
||||||
|
return match self {
|
||||||
|
GridElement::Water { amount } => {
|
||||||
|
if *amount == MAX_FLUID_AMOUNT {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
else if MAX_FLUID_AMOUNT - *amount < fill_amount {
|
||||||
|
return Some(MAX_FLUID_AMOUNT - *amount);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Some(fill_amount);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GridElement::Air => Some(fill_amount),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/grid/manipulation.rs
Normal file
31
src/grid/manipulation.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use bevy::math::UVec3;
|
||||||
|
use crate::grid::{Grid, GridElement};
|
||||||
|
|
||||||
|
pub struct GridManipulationTools<'a> {
|
||||||
|
grid: &'a mut Grid
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GridManipulationTools<'a> {
|
||||||
|
pub fn new(grid: &'a mut Grid) -> Self {
|
||||||
|
Self { grid }
|
||||||
|
}
|
||||||
|
pub fn set_xyz(&mut self,x: u32, y: u32, z: u32, element: GridElement) {
|
||||||
|
let index = self.grid.indexify(UVec3::new(x,y,z));
|
||||||
|
self.grid.data[index] = element;
|
||||||
|
}
|
||||||
|
pub fn fill_xyz(&mut self,x1: u32, y1: u32, z1: u32, x2: u32, y2: u32, z2: u32, element: GridElement) {
|
||||||
|
if x1 > x2 || y1 > y2 || z1 > z2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in x1..=x2 {
|
||||||
|
for y in y1..=y2 {
|
||||||
|
for z in z1..=z2 {
|
||||||
|
let index = self.grid.indexify(UVec3::new(x,y,z));
|
||||||
|
|
||||||
|
self.grid.data[index] = element.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/grid/mod.rs
Normal file
86
src/grid/mod.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
use bevy::{math::UVec3,prelude::*};
|
||||||
|
|
||||||
|
use crate::MAX_FLUID_AMOUNT;
|
||||||
|
use manipulation::*;
|
||||||
|
|
||||||
|
pub use element::{GridElement};
|
||||||
|
|
||||||
|
|
||||||
|
pub mod element;
|
||||||
|
pub mod manipulation;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Resource,Default,Clone)]
|
||||||
|
pub struct Grid {
|
||||||
|
pub(crate) size: UVec3,
|
||||||
|
pub(crate) data: Vec<GridElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
pub fn cube(side: u32) -> Self {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
let uside = side as usize;
|
||||||
|
data.resize(uside*uside*uside, GridElement::Air);
|
||||||
|
Self {
|
||||||
|
size: UVec3::new(side,side,side),
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn indexify(&self, vector: UVec3) -> usize{
|
||||||
|
let clamped_vector = vector.min(self.size - UVec3::ONE);
|
||||||
|
return (clamped_vector.x + self.size.x * clamped_vector.y + self.size.x * self.size.y * clamped_vector.z) as usize;
|
||||||
|
}
|
||||||
|
pub fn deindexify(&self, index: usize) -> UVec3 {
|
||||||
|
let undex = index as u32;
|
||||||
|
return UVec3::new(
|
||||||
|
undex % self.size.x,
|
||||||
|
undex / self.size.x % self.size.y,
|
||||||
|
undex / (self.size.x*self.size.y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pub fn tranfer_fluid(&mut self,from: usize, to: usize, transfer_amount: u32) {
|
||||||
|
let bounds = self.indexify(self.size);
|
||||||
|
if from >= bounds || to >= bounds {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let from_new_value;
|
||||||
|
let to_new_value;
|
||||||
|
|
||||||
|
match (self.data[to],self.data[from]) {
|
||||||
|
(GridElement::Air,GridElement::Water { amount: mut water_amount }) => {
|
||||||
|
to_new_value = Some(GridElement::Water { amount: transfer_amount });
|
||||||
|
water_amount = water_amount.checked_sub(transfer_amount).unwrap_or(0);
|
||||||
|
from_new_value = if water_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: water_amount })};
|
||||||
|
},
|
||||||
|
(GridElement::Water { amount: mut to_amount }, GridElement::Water { amount: mut from_amount }) => {
|
||||||
|
let transfer_amount = from_amount.min(transfer_amount);
|
||||||
|
let delta = MAX_FLUID_AMOUNT - to_amount;
|
||||||
|
if delta < transfer_amount {
|
||||||
|
to_amount = MAX_FLUID_AMOUNT;
|
||||||
|
from_amount -= delta;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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 })};
|
||||||
|
}
|
||||||
|
_ => {return;},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = to_new_value {
|
||||||
|
self.data[to] = value;
|
||||||
|
}
|
||||||
|
if let Some(value) = from_new_value {
|
||||||
|
self.data[from] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
pub fn manipulate<'a>(&'a mut self) -> GridManipulationTools<'a> {
|
||||||
|
GridManipulationTools::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
376
src/main.rs
376
src/main.rs
|
|
@ -1,7 +1,10 @@
|
||||||
use bevy::{math::UVec3, prelude::*};
|
use bevy::prelude::*;
|
||||||
|
use crate::grid::Grid;
|
||||||
|
use crate::simulation::tick;
|
||||||
|
|
||||||
//vibecode
|
pub mod simulation;
|
||||||
use bevy::color::palettes::css::{DARK_GRAY,ROYAL_BLUE};
|
pub mod view;
|
||||||
|
pub mod grid;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
|
|
@ -9,9 +12,6 @@ fn main() {
|
||||||
.insert_resource(Grid::cube(16))
|
.insert_resource(Grid::cube(16))
|
||||||
.insert_resource(Time::<Fixed>::from_hz(120.))
|
.insert_resource(Time::<Fixed>::from_hz(120.))
|
||||||
.add_systems(FixedUpdate, tick)
|
.add_systems(FixedUpdate, tick)
|
||||||
//Vibecode
|
|
||||||
.add_systems(Startup, setup_visuals)
|
|
||||||
.add_systems(Update, sync_visuals)
|
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,371 +19,7 @@ const MAX_FLUID_AMOUNT: u32 = 1000;
|
||||||
const MAX_VERTICAL_FLOW: u32 = MAX_FLUID_AMOUNT;
|
const MAX_VERTICAL_FLOW: u32 = MAX_FLUID_AMOUNT;
|
||||||
const MAX_HORIZONTAL_FLOW: u32 = MAX_FLUID_AMOUNT/2;
|
const MAX_HORIZONTAL_FLOW: u32 = MAX_FLUID_AMOUNT/2;
|
||||||
|
|
||||||
#[derive(Default,Clone,Copy)]
|
|
||||||
enum GridElement{
|
|
||||||
#[default]
|
|
||||||
Air,
|
|
||||||
Water{amount: u32},
|
|
||||||
Solid,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GridElement {
|
|
||||||
pub fn try_fill(&self, fill_amount: u32) -> Option<u32> {
|
|
||||||
return match self {
|
|
||||||
GridElement::Water { amount } => {
|
|
||||||
if *amount == MAX_FLUID_AMOUNT {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
else if MAX_FLUID_AMOUNT - *amount < fill_amount {
|
|
||||||
return Some(MAX_FLUID_AMOUNT - *amount);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Some(fill_amount);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GridElement::Air => Some(fill_amount),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource,Default,Clone)]
|
|
||||||
struct Grid {
|
|
||||||
size: UVec3,
|
|
||||||
data: Vec<GridElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Grid {
|
|
||||||
pub fn cube(side: u32) -> Self {
|
|
||||||
let mut data = Vec::new();
|
|
||||||
let uside = side as usize;
|
|
||||||
data.resize(uside*uside*uside, GridElement::Air);
|
|
||||||
Self {
|
|
||||||
size: UVec3::new(side,side,side),
|
|
||||||
data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn indexify(&self, vector: UVec3) -> usize{
|
|
||||||
let clamped_vector = vector.min(self.size - UVec3::ONE);
|
|
||||||
return (clamped_vector.x + self.size.x * clamped_vector.y + self.size.x * self.size.y * clamped_vector.z) as usize;
|
|
||||||
}
|
|
||||||
pub fn deindexify(&self, index: usize) -> UVec3 {
|
|
||||||
let undex = index as u32;
|
|
||||||
return UVec3::new(
|
|
||||||
undex % self.size.x,
|
|
||||||
undex / self.size.x % self.size.y,
|
|
||||||
undex / (self.size.x*self.size.y)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/*pub fn extract(&mut self, index: usize, extract_amount: u32) -> u32 {
|
|
||||||
if index >= self.indexify(self.size) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cleanup = false;
|
|
||||||
let mut extracted_amount = 0;
|
|
||||||
if let GridElement::Water { amount } = &mut self.data[index] {
|
|
||||||
if extract_amount >= *amount {
|
|
||||||
extracted_amount = amount.clone();
|
|
||||||
*amount = 0;
|
|
||||||
cleanup = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
*amount -= extract_amount;
|
|
||||||
extracted_amount = extract_amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cleanup {
|
|
||||||
self.data[index] = GridElement::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
return extracted_amount;
|
|
||||||
}
|
|
||||||
pub fn fill(&mut self, index: usize, fill_amount: u32) -> u32 {
|
|
||||||
if index >= self.indexify(self.size) {
|
|
||||||
return fill_amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let GridElement::None = self.data[index] {
|
|
||||||
self.data[index] = GridElement::Water { amount: fill_amount }
|
|
||||||
}
|
|
||||||
else if let GridElement::Water { amount } = &mut self.data[index] {
|
|
||||||
*amount = MAX_FLUID_AMOUNT.min(*amount + fill_amount);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
pub fn tranfer_fluid(&mut self,from: usize, to: usize, transfer_amount: u32) {
|
|
||||||
let bounds = self.indexify(self.size);
|
|
||||||
if from >= bounds || to >= bounds {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let from_new_value;
|
|
||||||
let to_new_value;
|
|
||||||
|
|
||||||
match (self.data[to],self.data[from]) {
|
|
||||||
(GridElement::Air,GridElement::Water { amount: mut water_amount }) => {
|
|
||||||
to_new_value = Some(GridElement::Water { amount: transfer_amount });
|
|
||||||
water_amount = water_amount.checked_sub(transfer_amount).unwrap_or(0);
|
|
||||||
from_new_value = if water_amount == 0 {Some(GridElement::Air)} else {Some(GridElement::Water { amount: water_amount })};
|
|
||||||
},
|
|
||||||
(GridElement::Water { amount: mut to_amount }, GridElement::Water { amount: mut from_amount }) => {
|
|
||||||
let transfer_amount = from_amount.min(transfer_amount);
|
|
||||||
let delta = MAX_FLUID_AMOUNT - to_amount;
|
|
||||||
if delta < transfer_amount {
|
|
||||||
to_amount = MAX_FLUID_AMOUNT;
|
|
||||||
from_amount -= delta;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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 })};
|
|
||||||
}
|
|
||||||
_ => {return;},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(value) = to_new_value {
|
|
||||||
self.data[to] = value;
|
|
||||||
}
|
|
||||||
if let Some(value) = from_new_value {
|
|
||||||
self.data[from] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
pub fn manipulate<'a>(&'a mut self) -> GridManipulationTools<'a> {
|
|
||||||
GridManipulationTools { grid: self }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GridManipulationTools<'a> {
|
|
||||||
grid: &'a mut Grid
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> GridManipulationTools<'a> {
|
|
||||||
pub fn set_xyz(&mut self,x: u32, y: u32, z: u32, element: GridElement) {
|
|
||||||
let index = self.grid.indexify(UVec3::new(x,y,z));
|
|
||||||
self.grid.data[index] = element;
|
|
||||||
}
|
|
||||||
pub fn fill_xyz(&mut self,x1: u32, y1: u32, z1: u32, x2: u32, y2: u32, z2: u32, element: GridElement) {
|
|
||||||
if x1 > x2 || y1 > y2 || z1 > z2 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for x in x1..=x2 {
|
|
||||||
for y in y1..=y2 {
|
|
||||||
for z in z1..=z2 {
|
|
||||||
let index = self.grid.indexify(UVec3::new(x,y,z));
|
|
||||||
|
|
||||||
self.grid.data[index] = element.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GridAction{
|
|
||||||
MoveLiquid{
|
|
||||||
from: UVec3,
|
|
||||||
to: UVec3,
|
|
||||||
amount: u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(mut grid: ResMut<Grid>) {
|
|
||||||
let mut stack: Vec<GridAction> = Vec::new();
|
|
||||||
|
|
||||||
for x in 0..grid.size.x {
|
|
||||||
for y in 0..grid.size.y {
|
|
||||||
for z in 0..grid.size.z {
|
|
||||||
let vector = UVec3::new(x,y,z);
|
|
||||||
let index = grid.indexify(vector);
|
|
||||||
|
|
||||||
match grid.data[index as usize] {
|
|
||||||
GridElement::Water { amount } => {
|
|
||||||
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)]
|
|
||||||
.try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) {
|
|
||||||
stack.push(GridAction::MoveLiquid { from: vector, to: gravity_vector, amount: gravity_amount });
|
|
||||||
transfer_amount -= gravity_amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transfer_amount = transfer_amount.min(MAX_HORIZONTAL_FLOW) / 4;
|
|
||||||
if transfer_amount > 0 {
|
|
||||||
for lookup_vector in [UVec3::new(1,0,0),UVec3::new(0,0,1)] {
|
|
||||||
if let Some(toward_vector) = vector.checked_add(lookup_vector) {
|
|
||||||
if let Some(toward_amount) = grid.data
|
|
||||||
[grid.indexify(toward_vector)]
|
|
||||||
.try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) {
|
|
||||||
stack.push(GridAction::MoveLiquid { from: vector, to: toward_vector, amount: toward_amount });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(toward_vector) = vector.checked_sub(lookup_vector) {
|
|
||||||
if let Some(toward_amount) = grid.data
|
|
||||||
[grid.indexify(toward_vector)]
|
|
||||||
.try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) {
|
|
||||||
stack.push(GridAction::MoveLiquid { from: vector, to: toward_vector, amount: toward_amount });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for action in stack {
|
|
||||||
match action {
|
|
||||||
GridAction::MoveLiquid { from, to, amount } => {
|
|
||||||
let (from_index, to_index) = (grid.indexify(from), grid.indexify(to));
|
|
||||||
grid.tranfer_fluid(from_index, to_index, amount);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Vibe code
|
|
||||||
|
|
||||||
const VISUAL_LEVELS: usize = 8;
|
|
||||||
|
|
||||||
/// Materials bucketed by fill level, plus a material for solid blocks.
|
|
||||||
#[derive(Resource)]
|
|
||||||
struct GridMaterials {
|
|
||||||
water_levels: Vec<Handle<StandardMaterial>>,
|
|
||||||
solid: Handle<StandardMaterial>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Marker for a per-cell visual cube.
|
|
||||||
#[derive(Component)]
|
|
||||||
struct CellVisual;
|
|
||||||
|
|
||||||
/// Maps grid index -> spawned entity, so `sync_visuals` can update in O(1)
|
|
||||||
/// per cell without searching.
|
|
||||||
#[derive(Resource)]
|
|
||||||
struct CellEntities(Vec<Entity>);
|
|
||||||
|
|
||||||
fn setup_visuals(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut grid: ResMut<Grid>,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
) {
|
|
||||||
let mut builder = grid.manipulate();
|
|
||||||
builder.fill_xyz(0, 0, 0, 6, 6, 6, GridElement::Solid);
|
|
||||||
builder.fill_xyz(1, 1, 1, 5, 6, 5, GridElement::Water { amount: MAX_FLUID_AMOUNT });
|
|
||||||
builder.set_xyz(6,1,3,GridElement::Air);
|
|
||||||
|
|
||||||
let center = grid.size.as_vec3() * 0.5;
|
|
||||||
|
|
||||||
// Camera positioned to see the whole cube from an angle.
|
|
||||||
commands.spawn((
|
|
||||||
Camera3d::default(),
|
|
||||||
Transform::from_xyz(
|
|
||||||
center.x + grid.size.x as f32 * 1.4,
|
|
||||||
grid.size.y as f32 * 1.8,
|
|
||||||
center.z + grid.size.z as f32 * 1.4,
|
|
||||||
)
|
|
||||||
.looking_at(center, Vec3::Y),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Simple directional light.
|
|
||||||
commands.spawn((
|
|
||||||
DirectionalLight {
|
|
||||||
illuminance: 8_000.0,
|
|
||||||
shadow_maps_enabled: false,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Transform::from_xyz(10.0, 30.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
||||||
));
|
|
||||||
|
|
||||||
let cube_mesh = meshes.add(Cuboid::new(0.9, 0.9, 0.9));
|
|
||||||
|
|
||||||
// Pre-build a handful of translucent blue materials, one per "fullness" bucket,
|
|
||||||
// so we don't need a unique material asset per cell.
|
|
||||||
let water_levels: Vec<Handle<StandardMaterial>> = (0..VISUAL_LEVELS)
|
|
||||||
.map(|i| {
|
|
||||||
let t = i as f32 / (VISUAL_LEVELS - 1) as f32;
|
|
||||||
materials.add(StandardMaterial {
|
|
||||||
base_color: Color::from(ROYAL_BLUE).with_alpha(0.15 + 0.75 * t),
|
|
||||||
alpha_mode: AlphaMode::Blend,
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let solid = materials.add(StandardMaterial {
|
|
||||||
base_color: Color::from(DARK_GRAY),
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Spawn one cube per grid cell, in the same order that `Grid::indexify`
|
|
||||||
// expects (x fastest, then y, then z), so `entities[i]` lines up with
|
|
||||||
// `grid.data[i]`. All start hidden.
|
|
||||||
let mut entities = Vec::with_capacity(grid.data.len());
|
|
||||||
for z in 0..grid.size.z {
|
|
||||||
for y in 0..grid.size.y {
|
|
||||||
for x in 0..grid.size.x {
|
|
||||||
let pos = UVec3::new(x, y, z).as_vec3() + Vec3::splat(0.5);
|
|
||||||
let entity = commands
|
|
||||||
.spawn((
|
|
||||||
Mesh3d(cube_mesh.clone()),
|
|
||||||
MeshMaterial3d(water_levels[0].clone()),
|
|
||||||
Transform::from_translation(pos),
|
|
||||||
Visibility::Hidden,
|
|
||||||
CellVisual,
|
|
||||||
))
|
|
||||||
.id();
|
|
||||||
entities.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.insert_resource(GridMaterials { water_levels, solid });
|
|
||||||
commands.insert_resource(CellEntities(entities));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync_visuals(
|
|
||||||
grid: Res<Grid>,
|
|
||||||
grid_materials: Res<GridMaterials>,
|
|
||||||
cell_entities: Res<CellEntities>,
|
|
||||||
mut query: Query<(&mut Visibility, &mut MeshMaterial3d<StandardMaterial>), With<CellVisual>>,
|
|
||||||
) {
|
|
||||||
// Grid is a plain Resource (not ResMut here), so `is_changed` only
|
|
||||||
// fires on frames where `tick` actually ran and mutated it.
|
|
||||||
if !grid.is_changed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index, element) in grid.data.iter().enumerate() {
|
|
||||||
let entity = cell_entities.0[index];
|
|
||||||
let Ok((mut visibility, mut material)) = query.get_mut(entity) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
match element {
|
|
||||||
GridElement::Water { amount } => {
|
|
||||||
*visibility = Visibility::Visible;
|
|
||||||
let level = ((*amount as u64 * (VISUAL_LEVELS as u64 - 1))
|
|
||||||
/ MAX_FLUID_AMOUNT as u64) as usize;
|
|
||||||
material.0 = grid_materials.water_levels[level].clone();
|
|
||||||
}
|
|
||||||
GridElement::Solid => {
|
|
||||||
*visibility = Visibility::Visible;
|
|
||||||
material.0 = grid_materials.solid.clone();
|
|
||||||
}
|
|
||||||
GridElement::Air => {
|
|
||||||
*visibility = Visibility::Hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
71
src/simulation.rs
Normal file
71
src/simulation.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use bevy::{prelude::*,math::UVec3};
|
||||||
|
use crate::{MAX_VERTICAL_FLOW,MAX_HORIZONTAL_FLOW,grid::{Grid,GridElement}};
|
||||||
|
|
||||||
|
enum GridAction{
|
||||||
|
MoveLiquid{
|
||||||
|
from: UVec3,
|
||||||
|
to: UVec3,
|
||||||
|
amount: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(mut grid: ResMut<Grid>) {
|
||||||
|
let mut stack: Vec<GridAction> = Vec::new();
|
||||||
|
|
||||||
|
for x in 0..grid.size.x {
|
||||||
|
for y in 0..grid.size.y {
|
||||||
|
for z in 0..grid.size.z {
|
||||||
|
let vector = UVec3::new(x,y,z);
|
||||||
|
let index = grid.indexify(vector);
|
||||||
|
|
||||||
|
match grid.data[index as usize] {
|
||||||
|
GridElement::Water { amount } => {
|
||||||
|
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)]
|
||||||
|
.try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) {
|
||||||
|
stack.push(GridAction::MoveLiquid { from: vector, to: gravity_vector, amount: gravity_amount });
|
||||||
|
transfer_amount -= gravity_amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer_amount = transfer_amount.min(MAX_HORIZONTAL_FLOW) / 4;
|
||||||
|
if transfer_amount > 0 {
|
||||||
|
for lookup_vector in [UVec3::new(1,0,0),UVec3::new(0,0,1)] {
|
||||||
|
if let Some(toward_vector) = vector.checked_add(lookup_vector) {
|
||||||
|
if let Some(toward_amount) = grid.data
|
||||||
|
[grid.indexify(toward_vector)]
|
||||||
|
.try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) {
|
||||||
|
stack.push(GridAction::MoveLiquid { from: vector, to: toward_vector, amount: toward_amount });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(toward_vector) = vector.checked_sub(lookup_vector) {
|
||||||
|
if let Some(toward_amount) = grid.data
|
||||||
|
[grid.indexify(toward_vector)]
|
||||||
|
.try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) {
|
||||||
|
stack.push(GridAction::MoveLiquid { from: vector, to: toward_vector, amount: toward_amount });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for action in stack {
|
||||||
|
match action {
|
||||||
|
GridAction::MoveLiquid { from, to, amount } => {
|
||||||
|
let (from_index, to_index) = (grid.indexify(from), grid.indexify(to));
|
||||||
|
grid.tranfer_fluid(from_index, to_index, amount);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
0
src/view/mod.rs
Normal file
0
src/view/mod.rs
Normal file
Loading…
Add table
Add a link
Reference in a new issue