summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-29 21:26:41 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-29 21:26:41 -0700
commita57cb58602b323469861c0b06573c6e47db56f34 (patch)
treeef00d97d1843f35472473a0ed8a3dedbfa9b43b9
parente0feb1dc1821f3e1e3d6b9697cd4560b377988b8 (diff)
generating wood caves
-rw-r--r--Cargo.lock10
-rw-r--r--Cargo.toml1
-rw-r--r--src/main.rs32
-rw-r--r--src/proc_gen/mod.rs48
-rw-r--r--src/renderer/mod.rs12
-rw-r--r--src/renderer/ui.rs23
-rw-r--r--src/sim/cell_manager/manager.rs16
7 files changed, 122 insertions, 20 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ba9ddca..f351d12 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -833,6 +833,15 @@ dependencies = [
]
[[package]]
+name = "fastnoise-lite"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e5f3c3cc9081e5d0e18bcd50e80cd33cba47fc22f88a9da9c33ecd1c87ea5c0"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
name = "fax"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2445,6 +2454,7 @@ dependencies = [
"egui-wgpu",
"egui-winit",
"env_logger",
+ "fastnoise-lite",
"futures",
"fxhash",
"glam 0.33.3",
diff --git a/Cargo.toml b/Cargo.toml
index e2ba7e4..03a3be6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,6 +36,7 @@ glam = "0.33.3"
png = "0.18.1"
toml = "1.1.4"
serde = "1.0.229"
+fastnoise-lite = "1.1.1"
[features]
profiler = []
diff --git a/src/main.rs b/src/main.rs
index 9c702a8..0284aaf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,7 @@ mod camera;
mod config;
mod content;
mod input;
+mod proc_gen;
mod renderer;
mod sim;
mod sprite_loader;
@@ -30,6 +31,7 @@ use crate::{
Input::{self},
InputManager,
},
+ proc_gen::WorldGenerator,
renderer::RendererState,
sim::{entity::EntityDef, rb_manager::DebugRenderMode, sim_manager::SimManager},
vfx::VfxManager,
@@ -46,6 +48,10 @@ struct Config {
cells_render: bool,
debug_render: bool,
debug_render_mode: DebugRenderMode,
+
+ // proc gen
+ proc_gen_seed: i32,
+ cave_noise_freq: f32,
}
struct Diagnostics {
@@ -63,6 +69,7 @@ struct App {
camera: Option<Camera>,
vfx_manager: Option<VfxManager>,
sim_manager: Option<SimManager>,
+ world_generator: Option<WorldGenerator>,
// renderer
last_render: Instant,
@@ -123,6 +130,18 @@ impl App {
impl Default for App {
fn default() -> Self {
+ let config = Config {
+ use_threading: true,
+ brush_radius: 10.0,
+ brush_material: MaterialId::Sand,
+ dropper_material: MaterialId::Wood,
+ cells_render: true,
+ debug_render: false,
+ debug_render_mode: DebugRenderMode::default(),
+
+ proc_gen_seed: 10,
+ cave_noise_freq: 0.005,
+ };
Self {
window: None,
renderer_state: None,
@@ -133,18 +152,11 @@ impl Default for App {
vfx_manager: Some(VfxManager::new()),
sim_manager: Some(SimManager::new()),
+ world_generator: Some(WorldGenerator::new(&config)),
last_render: Instant::now(),
- config: Config {
- use_threading: true,
- brush_radius: 10.0,
- brush_material: MaterialId::Sand,
- dropper_material: MaterialId::Wood,
- cells_render: true,
- debug_render: false,
- debug_render_mode: DebugRenderMode::default(),
- },
+ config,
diagnostics: Diagnostics {
fps: 0.0,
frame_times: VecDeque::new(),
@@ -227,6 +239,7 @@ impl ApplicationHandler for App {
&& let Some(sim) = &mut self.sim_manager
&& let Some(vfx) = &mut self.vfx_manager
&& let Some(camera) = &mut self.camera
+ && let Some(world_generator) = &mut self.world_generator
{
renderer_state.render(
sim,
@@ -235,6 +248,7 @@ impl ApplicationHandler for App {
&mut self.config,
&self.diagnostics,
&self.input_manager,
+ world_generator,
);
vfx.after_render(delta_time);
diff --git a/src/proc_gen/mod.rs b/src/proc_gen/mod.rs
new file mode 100644
index 0000000..8c965fb
--- /dev/null
+++ b/src/proc_gen/mod.rs
@@ -0,0 +1,48 @@
+use fastnoise_lite::FastNoiseLite;
+use glam::{IVec2, Vec2};
+
+use crate::{
+ Config,
+ config::CHUNK_SIZE,
+ content::materials::MaterialId,
+ sim::{cell::Cell, cell_manager::chunk::Chunk},
+};
+
+pub struct WorldGenerator {
+ cave_noise: FastNoiseLite,
+}
+
+impl WorldGenerator {
+ pub fn generate_chunk(&mut self, chunk_position: IVec2) -> Chunk {
+ let mut c = Chunk::void();
+ let position = chunk_position * CHUNK_SIZE;
+ for x in 0..CHUNK_SIZE {
+ for y in 0..CHUNK_SIZE {
+ let cell_position = position.as_vec2() + Vec2::new(x as f32, y as f32);
+ let cnv = self
+ .cave_noise
+ .get_noise_2d(cell_position.x, cell_position.y);
+ if cnv < 0.5 {
+ c.set_cell_at_local_position(
+ x as u8,
+ y as u8,
+ Cell::from_material(MaterialId::Wood),
+ );
+ }
+ }
+ }
+ c
+ }
+
+ pub fn update_params(&mut self, config: &Config) {
+ self.cave_noise = FastNoiseLite::with_seed(config.proc_gen_seed + 50);
+ self.cave_noise.set_frequency(Some(config.cave_noise_freq));
+ }
+
+ pub fn new(config: &Config) -> Self {
+ let cave_noise = FastNoiseLite::with_seed(config.proc_gen_seed + 50);
+ let mut wg = WorldGenerator { cave_noise };
+ wg.update_params(config);
+ wg
+ }
+}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 127a785..b5508e2 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -10,6 +10,7 @@ use crate::{
camera::Camera,
config::{CELLS_IN_CHUNK, CHUNK_SIZE},
content::{materials::MaterialId, vfx::VfxMaterialId},
+ proc_gen::WorldGenerator,
renderer::ui::draw_egui,
sim::{
cell_manager::manager::CellManager, entity::EntityId,
@@ -772,6 +773,7 @@ impl RendererState {
config: &mut Config,
diagnostics: &Diagnostics,
input_manager: &InputManager,
+ world_generator: &mut WorldGenerator,
) {
puffin::profile_function!();
@@ -819,7 +821,15 @@ impl RendererState {
.resizable(false)
// TODO collapse button
.show_collapsible(ui, &mut true, |panel_ui| {
- draw_egui(panel_ui, config, camera, diagnostics, input_manager, sim)
+ draw_egui(
+ panel_ui,
+ config,
+ camera,
+ diagnostics,
+ input_manager,
+ sim,
+ world_generator,
+ )
});
});
diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs
index 02ebb48..9af0113 100644
--- a/src/renderer/ui.rs
+++ b/src/renderer/ui.rs
@@ -1,10 +1,12 @@
use egui::{Color32, Stroke, Ui, epaint::CircleShape};
+use glam::IVec2;
use rapier2d::pipeline::QueryFilter;
use crate::{
Camera, Config, Diagnostics,
content::materials::MaterialId,
input::InputManager,
+ proc_gen::WorldGenerator,
sim::{rb_manager::DebugRenderMode, sim_manager::SimManager},
};
@@ -23,7 +25,8 @@ pub fn draw_egui<'a>(
camera: &mut Camera,
diagnostics: &Diagnostics,
input_manager: &InputManager,
- sim: &SimManager,
+ sim: &mut SimManager,
+ world_generator: &mut WorldGenerator,
) {
puffin::profile_function!();
ui.heading("Config");
@@ -104,7 +107,19 @@ pub fn draw_egui<'a>(
ui.label(format!("Real FPS: {}", diagnostics.fps));
ui.heading("Camera");
ui.add(egui::Slider::new(&mut camera.zoom, 0.0..=10.0).text("Zoom"));
- ui.heading("Input");
+ ui.heading("Proc gen");
+ if ui.button("Generate").clicked() {
+ world_generator.update_params(config);
+ for cx in -5..5 {
+ for cy in -5..5 {
+ let chunk = world_generator.generate_chunk(IVec2::new(cx, cy));
+ sim.cell_manager.upsert(cx, cy, chunk);
+ }
+ }
+ }
+ ui.add(egui::Slider::new(&mut config.proc_gen_seed, -100000..=100000).text("Seed"));
+ ui.label("Cave noise");
+ ui.add(egui::Slider::new(&mut config.cave_noise_freq, 0.0..=1.0).text("Frequency"));
ui.label(format!(
"Mouse (world): x,y=({x}, {y})",
@@ -118,13 +133,13 @@ pub fn draw_egui<'a>(
y = input_manager.chunk_mouse_pos.y
));
- if let Some(&chunk) = sim.cell_manager.chunk_position_to_chunk_idx.get(&(
+ if let Some(&idx) = sim.cell_manager.chunk_position_to_chunk_idx.get(&(
input_manager.chunk_mouse_pos.x,
input_manager.chunk_mouse_pos.y,
)) {
ui.label(format!(
"Sleeping={}",
- sim.cell_manager.chunks[chunk].sleeping
+ sim.cell_manager.chunks[idx].sleeping
));
}
diff --git a/src/sim/cell_manager/manager.rs b/src/sim/cell_manager/manager.rs
index 64b97f1..c8015f7 100644
--- a/src/sim/cell_manager/manager.rs
+++ b/src/sim/cell_manager/manager.rs
@@ -48,10 +48,14 @@ impl CellManager {
}
}
- pub fn insert(&mut self, x: i32, y: i32, chunk: Chunk) {
- self.chunk_position_to_chunk_idx
- .insert((x, y), self.chunks.len());
- self.chunks.push(chunk);
+ pub fn upsert(&mut self, x: i32, y: i32, chunk: Chunk) {
+ if let Some(&idx) = self.chunk_position_to_chunk_idx.get(&(x, y)) {
+ self.chunks[idx] = chunk;
+ } else {
+ self.chunk_position_to_chunk_idx
+ .insert((x, y), self.chunks.len());
+ self.chunks.push(chunk);
+ }
}
pub fn tick(&mut self, use_threading: bool) {
@@ -70,9 +74,9 @@ impl CellManager {
for y in -10..1 {
for x in -100..100 {
if y == 0 {
- world.insert(x, y, Chunk::floor());
+ world.upsert(x, y, Chunk::floor());
} else {
- world.insert(x, y, Chunk::void());
+ world.upsert(x, y, Chunk::void());
}
}
}