Initial commit
This commit is contained in:
commit
1119f7b0a0
4 changed files with 6424 additions and 0 deletions
389
src/main.rs
Normal file
389
src/main.rs
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
use bevy::{math::UVec3, prelude::*};
|
||||
|
||||
//vibecode
|
||||
use bevy::color::palettes::css::{DARK_GRAY,ROYAL_BLUE};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.insert_resource(Grid::cube(16))
|
||||
.insert_resource(Time::<Fixed>::from_hz(120.))
|
||||
.add_systems(FixedUpdate, tick)
|
||||
//Vibecode
|
||||
.add_systems(Startup, setup_visuals)
|
||||
.add_systems(Update, sync_visuals)
|
||||
.run();
|
||||
}
|
||||
|
||||
const MAX_FLUID_AMOUNT: u32 = 1000;
|
||||
const MAX_VERTICAL_FLOW: u32 = MAX_FLUID_AMOUNT;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue