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