refactor file locations for readability
This commit is contained in:
@@ -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>,
|
||||
) {
|
||||
}
|
||||
@@ -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>,
|
||||
) {
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user