diff --git a/Cargo.toml b/Cargo.toml index f62084b..b76862e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] -bevy = "0.19.0" +bevy = { version = "0.19.0", features = ["bevy_feathers"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9611b56 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +GRES +Copyright (C) 2026 Veselov Fedor + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/src/grid/manipulation.rs b/src/grid/manipulation.rs index 3238ae0..c73e5b1 100644 --- a/src/grid/manipulation.rs +++ b/src/grid/manipulation.rs @@ -10,7 +10,9 @@ impl<'a> GridManipulationTools<'a> { 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)); + let Some(index) = self.grid.indexify(UVec3::new(x,y,z)) else { + return; + }; 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) { @@ -21,7 +23,9 @@ impl<'a> GridManipulationTools<'a> { 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)); + let Some(index) = self.grid.indexify(UVec3::new(x,y,z)) else { + continue; + }; self.grid.data[index] = element.clone(); } diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 79e8333..4c114aa 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -26,21 +26,26 @@ impl Grid { 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 cell_amount(&self) -> u32 { + return self.size.x * self.size.y * self.size.z; } - pub fn deindexify(&self, index: usize) -> UVec3 { - let undex = index as u32; + pub fn indexify(&self, vector: UVec3) -> Option{ + if vector.x >= self.size.x || vector.y >= self.size.y || vector.z >= self.size.z { + return None; + } + return Some((vector.x + self.size.x * vector.y + self.size.x * self.size.y * vector.z) as usize); + } + pub fn deindexify(&self, index: u32) -> UVec3 { return UVec3::new( - undex % self.size.x, - undex / self.size.x % self.size.y, - undex / (self.size.x*self.size.y) + index % self.size.x, + 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) { - let bounds = self.indexify(self.size); + let bounds = self.cell_amount() as usize; if from >= bounds || to >= bounds { + error!("Trying to transfer fluid out of bounds!"); return; } diff --git a/src/main.rs b/src/main.rs index 3823b7e..51bfa94 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,8 @@ use bevy::prelude::*; use crate::grid::Grid; use crate::simulation::tick; +use crate::view::ViewPlugin; + pub mod simulation; pub mod view; pub mod grid; @@ -9,8 +11,9 @@ pub mod grid; fn main() { App::new() .add_plugins(DefaultPlugins) + .add_plugins(ViewPlugin) .insert_resource(Grid::cube(16)) - .insert_resource(Time::::from_hz(120.)) + .insert_resource(Time::::from_hz(20.)) .add_systems(FixedUpdate, tick) .run(); } diff --git a/src/simulation.rs b/src/simulation.rs index b369d6b..3e37008 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -16,7 +16,9 @@ pub fn tick(mut grid: ResMut) { 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); + let Some(index) = grid.indexify(vector) else { + continue; + }; match grid.data[index as usize] { GridElement::Water { amount } => { @@ -24,7 +26,7 @@ pub fn tick(mut grid: ResMut) { if let Some(gravity_vector) = vector.checked_sub(UVec3::new(0,1,0)) { if let Some(gravity_amount) = grid.data - [grid.indexify(gravity_vector)] + [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; @@ -35,18 +37,22 @@ pub fn tick(mut grid: ResMut) { 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(index) = grid.indexify(toward_vector) { + if let Some(toward_amount) = grid.data + [index] + .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 }); + if let Some(index) = grid.indexify(toward_vector) { + if let Some(toward_amount) = grid.data + [index] + .try_fill(transfer_amount.min(MAX_VERTICAL_FLOW)) { + stack.push(GridAction::MoveLiquid { from: vector, to: toward_vector, amount: toward_amount }); + } } } } @@ -62,10 +68,11 @@ pub fn tick(mut grid: ResMut) { for action in stack { match action { GridAction::MoveLiquid { from, to, amount } => { - let (from_index, to_index) = (grid.indexify(from), grid.indexify(to)); + let (from_index, to_index) = (grid.indexify(from).unwrap(), grid.indexify(to).unwrap()); grid.tranfer_fluid(from_index, to_index, amount); }, } } + } diff --git a/src/view/game.rs b/src/view/game.rs new file mode 100644 index 0000000..1b791e5 --- /dev/null +++ b/src/view/game.rs @@ -0,0 +1,142 @@ +use bevy::prelude::*; +use crate::grid::Grid; +use crate::MAX_FLUID_AMOUNT; + +const FLUID_FULLNES_STAGES: usize = 7; + +pub struct GameViewPlugin; + +impl Plugin for GameViewPlugin { + fn build(&self, app: &mut App) { + app.add_systems(PreStartup, debug_setup_grid) + .init_resource::() + .init_resource::() + .add_systems(Startup, (setup_view_cubes, scene.spawn())) + .add_systems(Update, update_cubes); + } +} + +#[derive(Resource, Default)] +pub struct CubeStages(pub Vec>); + +#[derive(Component, FromTemplate)] +pub struct TileWatch(pub usize); + +#[derive(Resource, Default)] +pub struct TileMaterials { + pub solid: Handle, + pub water: Handle, +} + +pub fn debug_setup_grid(mut grid: ResMut) { + use crate::grid::GridElement; + let mut tool = grid.manipulate(); + tool.fill_xyz(5, 0, 0, 5, 0, 14, GridElement::Solid); + tool.fill_xyz(0, 0, 0, 4, 0, 15, GridElement::Water { amount: MAX_FLUID_AMOUNT }); +} + +pub fn setup_view_cubes( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + mut stages: ResMut, + mut tile_materials: ResMut, + grid: Res) { + for i in 0..FLUID_FULLNES_STAGES { + let tile_height = ((i+1) as f32)/(FLUID_FULLNES_STAGES as f32); + let mesh = meshes.add(Cuboid::new(1., tile_height, 1.)); + stages.0.push(mesh); + } + + tile_materials.water = materials.add(StandardMaterial { + base_color: Color::hsl(172.,0.502,0.571), + ..default() + }); + + tile_materials.solid = materials.add(StandardMaterial { + base_color: Color::hsl(268., 0.778, 0.499), + ..default() + }); + + let mesh = stages.0[FLUID_FULLNES_STAGES-1].clone(); + + for i in 0..grid.cell_amount() { + commands.spawn_scene( + cube( + grid.deindexify(i as u32).as_vec3(), + i as usize, + mesh.clone() + ) + ); + } +} + +pub fn scene() -> impl SceneList { + bsn_list![ + camera(), + sun() + ] +} + +pub fn camera() -> impl Scene { + bsn! { + Camera3d + template( + |ctx| { + let grid = ctx.resource::(); + let center = grid.size.as_vec3() * 0.5; + Ok(Transform::from_xyz( + center.x + grid.size.x as f32 * 1.5, + grid.size.y as f32 * 1.5, + center.z + grid.size.z as f32 * 1.5).looking_at(center, Vec3::Y)) + } + ) + } +} + +pub fn sun() -> impl Scene { + bsn!{ + DirectionalLight { + illuminance: 8_000.0, + shadow_maps_enabled: false, + } + template_value(Transform::from_xyz(10.0, 30.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y)) + } +} + + +pub fn cube(position: Vec3,index: usize,mesh: Handle) -> impl Scene { + bsn! { + Mesh3d(mesh) + MeshMaterial3d + Transform::from_translation(position) + TileWatch(index) + Visibility::Hidden + + } +} + +pub fn update_cubes(query: Query<(&mut Mesh3d,&mut Visibility,&mut MeshMaterial3d,&TileWatch)>, materials: Res, stages: Res, grid: Res) { + if grid.is_changed() == false { return; } + + for (mut mesh,mut visibility, mut material, index) in query { + let state = grid.data[index.0]; + + match state { + crate::grid::GridElement::Air => { + *visibility = Visibility::Hidden; + }, + crate::grid::GridElement::Water { amount } => { + *visibility = Visibility::Visible; + material.0 = materials.water.clone(); + let stage = ((amount as f32)/(MAX_FLUID_AMOUNT as f32) * (FLUID_FULLNES_STAGES-1) as f32).round() as usize; + mesh.0 = stages.0[stage].clone(); + }, + crate::grid::GridElement::Solid => { + *visibility = Visibility::Visible; + material.0 = materials.solid.clone(); + mesh.0 = stages.0[FLUID_FULLNES_STAGES-1].clone(); + }, + } + } +} diff --git a/src/view/mod.rs b/src/view/mod.rs index e69de29..fe3f2e2 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -0,0 +1,14 @@ +use bevy::prelude::*; + +use game::GameViewPlugin; + +pub mod ui; +pub mod game; + +pub struct ViewPlugin; + +impl Plugin for ViewPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(GameViewPlugin); + } +} diff --git a/src/view/ui.rs b/src/view/ui.rs new file mode 100644 index 0000000..e69de29