refactor file locations for readability

This commit is contained in:
2025-09-11 18:23:53 +01:00
parent e58f6804bf
commit ee7ac39ad5
25 changed files with 1103 additions and 1052 deletions
+11
View File
@@ -0,0 +1,11 @@
use bevy::prelude::*;
use crate::world::{tiles::TileMap, ChunkMap, GenerateChunkEvent};
pub fn generate_chunk_fauna(
mut commands: Commands,
mut events: EventReader<GenerateChunkEvent>,
mut chunk_map: ResMut<ChunkMap>,
mut tilemap: ResMut<TileMap>,
) {
}
+11
View File
@@ -0,0 +1,11 @@
use bevy::prelude::*;
use crate::world::{tiles::TileMap, ChunkMap, GenerateChunkEvent};
pub fn generate_chunk_foliage(
mut commands: Commands,
mut events: EventReader<GenerateChunkEvent>,
mut chunk_map: ResMut<ChunkMap>,
mut tilemap: ResMut<TileMap>,
) {
}
+176
View File
@@ -0,0 +1,176 @@
use bevy::prelude::*;
use bevy_platform::collections::HashSet;
use bevy_platform::sync::Mutex;
use bevy_platform::time::Instant;
use bevy_rand::prelude::*;
use rand::{Rng, SeedableRng};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use crate::{
constants::{SEED, TILE_SIZE},
world::{
tiles::TileMap, ChunkForrestryEvent, FixtureTilePrefab, TextureIDs, Textures,
VisibleGameEntity,
},
};
pub fn generate_chunk_forrestry(
commands: ParallelCommands<'_, '_>,
mut events: EventReader<ChunkForrestryEvent>,
mut tilemap: ResMut<TileMap>,
texture_ids: Res<TextureIDs>,
textures: Res<Textures>,
) {
let start = Instant::now();
let count = events.len();
let collected_tilemap_updates: Mutex<Vec<(IVec3, (i32, bool, bool, [u32; 8]))>> =
Mutex::new(Vec::<(IVec3, (i32, bool, bool, [u32; 8]))>::new());
events.par_read().for_each(|event| {
let floor_positions = &event.floor_tiles;
let mut tree_positions: Vec<Vec3> = Vec::new();
let min_distance = 7.0 * TILE_SIZE;
let mut hasher = DefaultHasher::new();
SEED.hash(&mut hasher);
event.chunk_position.x.hash(&mut hasher);
event.chunk_position.y.hash(&mut hasher);
let seed = hasher.finish();
let mut rng = WyRand::seed_from_u64(seed);
for (position, floor_type) in floor_positions.iter() {
let above_pos = *position + Vec3::new(0.0, 0.0, TILE_SIZE);
match floor_type.as_str() {
"grass" => {
commands.command_scope(|mut commands| {
// Track log positions to avoid leaf overlap
let mut log_positions = HashSet::new();
// Check if position is far enough from existing trees
let is_far_enough = tree_positions
.iter()
.all(|&tree_pos| above_pos.distance(tree_pos) > min_distance);
// 1 in 100 chance if far enough from other trees
if is_far_enough && rng.random::<u32>() % 100 == 0 {
// Generate trunk
let trunk_height = 4 + rng.random::<u32>() % 5;
for i in 0..trunk_height {
let trunk_pos =
above_pos + Vec3::new(0.0, 0.0, i as f32 * TILE_SIZE);
let trunk_ivec = trunk_pos.as_ivec3();
// Skip if position already has a tile
if tilemap.fixture_tiles.get(&trunk_ivec).is_some() {
continue;
}
let trunk_entity =
FixtureTilePrefab::log(trunk_pos).spawn(&mut commands);
tree_positions.push(trunk_pos);
// Add sprite component to the same entity if texture exists
if let Some(texture_id) = texture_ids.refs.get(&500004) {
if let Some(texture) = textures.handles.get(texture_id) {
let sprite = Sprite {
image: texture.clone(),
..Default::default()
};
commands
.entity(trunk_entity)
.insert((sprite, VisibleGameEntity));
// .insert(Visibility::Visible); // Override the hidden visibility
}
}
collected_tilemap_updates
.lock()
.unwrap()
.push((trunk_ivec, (1, false, true, [0; 8])));
log_positions.insert(trunk_ivec);
}
// Generate leaves - 3D spherical canopy with random shape
let base_leaf_radius = 2.25;
let leaf_center =
above_pos + Vec3::new(0.0, 0.0, trunk_height as f32 * TILE_SIZE);
for x in -base_leaf_radius as i32..=base_leaf_radius as i32 {
for y in -base_leaf_radius as i32..=base_leaf_radius as i32 {
for z in -base_leaf_radius as i32..=base_leaf_radius as i32 {
let pos = Vec3::new(
x as f32 * TILE_SIZE,
y as f32 * TILE_SIZE,
z as f32 * TILE_SIZE,
) + leaf_center;
let ivec = pos.as_ivec3();
// Skip if position already has a tile or is a log
if tilemap.fixture_tiles.get(&ivec).is_some()
|| log_positions.contains(&ivec)
{
continue;
}
// Apply random radius variation for natural shape
let x_f = x as f32;
let y_f = y as f32;
let z_f = z as f32;
let radius = base_leaf_radius
* (1.0 + (rng.random::<f32>() * 0.35 - 0.1));
if x_f * x_f + y_f * y_f + z_f * z_f <= radius * radius {
FixtureTilePrefab::leaves(pos).spawn(&mut commands);
if let Some(texture_id) = texture_ids.refs.get(&500005)
{
if let Some(texture) =
textures.handles.get(texture_id)
{
let sprite = Sprite {
image: texture.clone(),
..Default::default()
};
let leaf = commands
.spawn((
sprite,
Transform::from_xyz(
pos.x, pos.y, pos.z,
),
))
.id();
commands.entity(leaf).insert(VisibleGameEntity);
collected_tilemap_updates
.lock()
.unwrap()
.push((ivec, (5, false, true, [0; 8])));
}
}
}
}
}
}
}
});
}
_ => {}
}
}
});
let collected_updates = collected_tilemap_updates.into_inner().unwrap();
for (ivec, data) in collected_updates {
tilemap.fixture_tiles.insert(ivec, data);
}
if count > 0 {
println!(
"Forrestry update for {:?} chunks in {:.2?}",
count,
start.elapsed()
);
}
}
+9
View File
@@ -0,0 +1,9 @@
pub mod fauna;
pub mod foliage;
pub mod forestry;
pub mod terrain;
pub use fauna::*;
pub use foliage::*;
pub use forestry::*;
pub use terrain::*;
+174
View File
@@ -0,0 +1,174 @@
use bevy::prelude::*;
use bevy_platform::collections::HashMap;
use bevy_platform::sync::Mutex;
use bevy_platform::time::Instant;
use noise::{NoiseFn, Perlin};
use crate::{
constants::{SEED, TILE_SIZE},
world::{
tiles::TileMap, ChunkForrestryEvent, ChunkTerrainEvent, FloorTilePrefab,
TileOcclusionEvent, CHUNK_SIZE, Z_ABOVE, Z_BELOW,
},
};
pub fn generate_surface_terrain(x: i32, y: i32) -> f32 {
let noise = Perlin::new(SEED);
let mut noise_value = 0.0;
let mut amplitude = 1.0;
let mut frequency = 0.008;
for _ in 0..6 {
noise_value += noise.get([x as f64 * frequency, y as f64 * frequency]) * amplitude;
amplitude *= 0.6;
frequency *= 1.8;
}
(noise_value * 2.5) as f32
}
pub fn generate_chunk_terrain(
commands: ParallelCommands<'_, '_>, // Use ParallelCommands for parallel spawning
mut events: EventReader<ChunkTerrainEvent>,
mut tilemap: ResMut<TileMap>,
mut forrestry_event_writer: EventWriter<ChunkForrestryEvent>,
mut occlusion_event_writer: EventWriter<TileOcclusionEvent>,
) {
let is_empty = events.is_empty();
let start = Instant::now();
let count: usize = events.len();
let cave_noise = Perlin::new(SEED);
// Create mutexes for our shared resources
let tilemap_updates = Mutex::new(HashMap::new());
let forrestry_events = Mutex::new(Vec::new());
events.par_read().for_each(|event| {
let chunk_pos = event.chunk_position;
let start_x = chunk_pos.x * CHUNK_SIZE;
let start_y = chunk_pos.y * CHUNK_SIZE;
let mut surface_positions: Vec<(Vec3, String)> = Vec::new();
let mut local_tilemap_updates: HashMap<IVec3, (i32, bool, bool, bool, i32, [u32; 8])> =
HashMap::new();
// Generate tiles for this chunk
for local_y in 0..CHUNK_SIZE {
for local_x in 0..CHUNK_SIZE {
let world_x = start_x + local_x;
let world_y = start_y + local_y;
let noise_position = Vec3::new(
(world_x as f32 * TILE_SIZE).round(),
(world_y as f32 * TILE_SIZE).round(),
(generate_surface_terrain(world_x, world_y) * TILE_SIZE).round(),
);
// Spawn tiles and add them to tilemap
for z in -Z_BELOW as isize..=Z_ABOVE as isize {
let position = Vec3::new(
(world_x as f32 * TILE_SIZE).round(),
(world_y as f32 * TILE_SIZE).round(),
(z as f32 * TILE_SIZE).round(),
);
let pos_ivec = position.as_ivec3();
if z < -5 {
let cave_value = cave_noise.get([
world_x as f64 * 0.05,
world_y as f64 * 0.05,
z as f64 * 0.05,
]);
if cave_value < -0.75 {
commands.command_scope(|mut cmd| {
FloorTilePrefab::air(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (0, true, false, true, 0, [0; 8]));
// Air tile
} else if cave_value < 0.8 {
commands.command_scope(|mut cmd| {
FloorTilePrefab::rock(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (2, false, true, false, 50, [0; 8]));
// Rock tile
} else {
commands.command_scope(|mut cmd| {
FloorTilePrefab::dirt(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (1, false, true, false, 85, [0; 8]));
// Dirt tile
}
} else if noise_position.z > position.z {
if (generate_surface_terrain(world_x, world_y) * TILE_SIZE).round()
<= position.z + TILE_SIZE
{
commands.command_scope(|mut cmd| {
FloorTilePrefab::grass(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (1, false, true, false, 100, [0; 8])); // Dirt tile (grass)
surface_positions.push((position, ("grass").to_string()));
} else {
commands.command_scope(|mut cmd| {
FloorTilePrefab::dirt(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (1, false, true, false, 85, [0; 8]));
// Dirt tile
}
} else {
commands.command_scope(|mut cmd| {
FloorTilePrefab::air(position).spawn(&mut cmd);
});
local_tilemap_updates.insert(pos_ivec, (0, true, false, true, 0, [0; 8]));
// Air tile
}
}
}
}
// Add our local updates to the global mutexes
{
let mut tilemap_guard = tilemap_updates.lock().unwrap();
for (pos, data) in local_tilemap_updates {
tilemap_guard.insert(pos, data);
}
}
// Store forrestry event for this chunk
forrestry_events.lock().unwrap().push(ChunkForrestryEvent {
chunk_position: chunk_pos,
floor_tiles: surface_positions,
});
});
for (pos, data) in tilemap_updates.into_inner().unwrap() {
tilemap.floor_tiles.insert(pos, data);
occlusion_event_writer.write(TileOcclusionEvent { tile_position: pos });
}
// Send all forrestry events
for event in forrestry_events.into_inner().unwrap() {
forrestry_event_writer.write(event);
}
if !is_empty {
println!("{} terrain chunks loaded in {:.2?}", count, start.elapsed());
}
}
pub fn generate_chunk_weathering_and_precipitation(// mut commands: Commands,
// mut events: EventReader<GenerateChunkEvent>,
// mut chunk_map: ResMut<ChunkMap>,
// mut tilemap: ResMut<TileMap>,
) {
// TODO: Generate weathering and precipitation
// Temperature and humidity
// Erosion
// Hadley lines/cells etc
// Biomes
}