refactor file locations for readability
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
+4
-7
@@ -3,7 +3,8 @@ use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::camera::{OrthographicProjection, Projection};
|
use bevy::render::camera::{OrthographicProjection, Projection};
|
||||||
|
|
||||||
use crate::{game, tilemap};
|
use crate::game;
|
||||||
|
use crate::world::{Z_ABOVE, Z_BELOW};
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct PanningCamera {
|
pub struct PanningCamera {
|
||||||
@@ -21,17 +22,13 @@ pub fn camera_z_movement(
|
|||||||
camera_moved.0 = false;
|
camera_moved.0 = false;
|
||||||
if keyboard_input.just_pressed(KeyCode::ShiftLeft) {
|
if keyboard_input.just_pressed(KeyCode::ShiftLeft) {
|
||||||
z_index.0 -= 1.0;
|
z_index.0 -= 1.0;
|
||||||
z_index.0 = z_index
|
z_index.0 = z_index.0.clamp(-Z_BELOW + 1.0, Z_ABOVE - 1.);
|
||||||
.0
|
|
||||||
.clamp(-tilemap::Z_BELOW + 1.0, tilemap::Z_ABOVE - 1.);
|
|
||||||
camera_moved.0 = true;
|
camera_moved.0 = true;
|
||||||
println!("z_index = {}", z_index.0);
|
println!("z_index = {}", z_index.0);
|
||||||
}
|
}
|
||||||
if keyboard_input.just_pressed(KeyCode::ShiftRight) {
|
if keyboard_input.just_pressed(KeyCode::ShiftRight) {
|
||||||
z_index.0 += 1.0;
|
z_index.0 += 1.0;
|
||||||
z_index.0 = z_index
|
z_index.0 = z_index.0.clamp(-Z_BELOW + 1.0, Z_ABOVE - 1.);
|
||||||
.0
|
|
||||||
.clamp(-tilemap::Z_BELOW + 1.0, tilemap::Z_ABOVE - 1.);
|
|
||||||
camera_moved.0 = true;
|
camera_moved.0 = true;
|
||||||
println!("z_index = {}", z_index.0);
|
println!("z_index = {}", z_index.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::constants::TILE_SIZE;
|
use crate::constants::TILE_SIZE;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::entities::shared_components::Ambulatory;
|
use crate::entities::shared_components::Ambulatory;
|
||||||
use crate::tilemap::VisibleGameEntity;
|
use crate::world::VisibleGameEntity;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_rand::prelude::*;
|
use bevy_rand::prelude::*;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::constants::TILE_SIZE;
|
use crate::constants::TILE_SIZE;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::entities::shared_components::Ambulatory;
|
use crate::entities::shared_components::Ambulatory;
|
||||||
use crate::tilemap::VisibleGameEntity;
|
use crate::world::VisibleGameEntity;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_rand::prelude::*;
|
use bevy_rand::prelude::*;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::constants::TILE_SIZE;
|
use crate::constants::TILE_SIZE;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::entities::shared_components::Ambulatory;
|
use crate::entities::shared_components::Ambulatory;
|
||||||
use crate::tilemap::VisibleGameEntity;
|
use crate::world::VisibleGameEntity;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_rand::prelude::*;
|
use bevy_rand::prelude::*;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
use crate::constants::TILE_SIZE;
|
use crate::constants::TILE_SIZE;
|
||||||
use crate::tile::TileMap;
|
use crate::world::tiles::TileMap;
|
||||||
use crate::{
|
use crate::world::{chunks::ChunkMap, chunks::CHUNK_SIZE};
|
||||||
constants::*,
|
use crate::{constants::*, entities::shared_components::Ambulatory};
|
||||||
entities::shared_components::Ambulatory,
|
|
||||||
tilemap::{ChunkMap, CHUNK_SIZE},
|
|
||||||
};
|
|
||||||
use bevy::{math::ivec3, prelude::*};
|
use bevy::{math::ivec3, prelude::*};
|
||||||
use bevy_rand::prelude::*;
|
use bevy_rand::prelude::*;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|||||||
+6
-8
@@ -7,19 +7,17 @@ mod cursor;
|
|||||||
mod entities;
|
mod entities;
|
||||||
mod game;
|
mod game;
|
||||||
mod item;
|
mod item;
|
||||||
mod tile;
|
mod world;
|
||||||
mod tilemap;
|
|
||||||
mod tiles;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.insert_resource(game::ZIndex(0.0))
|
.insert_resource(game::ZIndex(0.0))
|
||||||
.insert_resource(tiles::CurrentWorldSpriteState {
|
.insert_resource(world::tiles::CurrentWorldSpriteState {
|
||||||
state: tiles::TerrainSpriteState::Inactive,
|
state: world::tiles::TerrainSpriteState::Inactive,
|
||||||
})
|
})
|
||||||
.insert_resource(camera::CameraMoved(false))
|
.insert_resource(camera::CameraMoved(false))
|
||||||
.insert_resource(tiles::QuiltCache::default())
|
.insert_resource(world::tiles::QuiltCache::default())
|
||||||
.add_systems(PreStartup, tiles::initialize_textures)
|
.add_systems(PreStartup, world::textures::initialize_textures)
|
||||||
.add_plugins(
|
.add_plugins(
|
||||||
DefaultPlugins
|
DefaultPlugins
|
||||||
.set(WindowPlugin {
|
.set(WindowPlugin {
|
||||||
@@ -35,7 +33,7 @@ fn main() {
|
|||||||
(constants::SEED as u64).to_le_bytes(),
|
(constants::SEED as u64).to_le_bytes(),
|
||||||
))
|
))
|
||||||
.insert_resource(ClearColor(Color::srgb(0., 0., 0.)))
|
.insert_resource(ClearColor(Color::srgb(0., 0., 0.)))
|
||||||
.add_plugins(tilemap::TilemapPlugin)
|
.add_plugins(world::WorldPlugin)
|
||||||
.add_plugins(entities::pathfinding::PathfindingPlugin)
|
.add_plugins(entities::pathfinding::PathfindingPlugin)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Startup,
|
Startup,
|
||||||
|
|||||||
-75
@@ -1,75 +0,0 @@
|
|||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
game, tilemap,
|
|
||||||
tiles::{self, TerrainSpriteState},
|
|
||||||
};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_platform::collections::hash_map::HashMap;
|
|
||||||
|
|
||||||
#[derive(Component, Clone)]
|
|
||||||
pub struct FloorTile {
|
|
||||||
pub id: u32,
|
|
||||||
pub opaque: bool,
|
|
||||||
pub walkable: bool,
|
|
||||||
pub astar_weight: u8,
|
|
||||||
pub visible_range: [u32; 8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FloorTile {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
id: 0,
|
|
||||||
opaque: true,
|
|
||||||
walkable: true,
|
|
||||||
astar_weight: 0,
|
|
||||||
visible_range: [0; 8],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Clone)]
|
|
||||||
pub struct FixtureTile {
|
|
||||||
pub id: u32,
|
|
||||||
pub solid: bool,
|
|
||||||
pub visible_range: [u32; 8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FixtureTile {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
id: 0,
|
|
||||||
solid: true,
|
|
||||||
visible_range: [0; 8],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource, Default, Clone)]
|
|
||||||
pub struct TileMap {
|
|
||||||
pub floor_tiles: HashMap<IVec3, (i32, bool, bool, bool, i32, [u32; 8])>, //id, canStandIn, canStandOn, visiblyTransparent, astar_weight, visible_range
|
|
||||||
pub fixture_tiles: HashMap<IVec3, (i32, bool, bool, [u32; 8])>, // id, canStandIn, canStandOn, visible_range
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_tile_visibility(
|
|
||||||
z_index: Res<game::ZIndex>,
|
|
||||||
mut query: Query<(&tiles::TerrainSprite, &mut Visibility)>,
|
|
||||||
mut cwss: ResMut<tiles::CurrentWorldSpriteState>,
|
|
||||||
) {
|
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
for (terrain_sprite, mut visibility) in query.iter_mut() {
|
|
||||||
*visibility = if terrain_sprite.z_index == ((z_index.0 + tilemap::Z_BELOW) as usize) {
|
|
||||||
Visibility::Visible
|
|
||||||
} else {
|
|
||||||
Visibility::Hidden
|
|
||||||
};
|
|
||||||
}
|
|
||||||
cwss.state = TerrainSpriteState::Inactive;
|
|
||||||
println!("Visibility update: {:.2?}", now.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct TileState {
|
|
||||||
pub timer: Timer,
|
|
||||||
}
|
|
||||||
-678
@@ -1,678 +0,0 @@
|
|||||||
use std::sync::Mutex;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use crate::constants::{self, ITILE_SIZE, TILE_SIZE};
|
|
||||||
use crate::tile::{update_tile_visibility, FloorTile, TileMap};
|
|
||||||
use crate::tiles::{
|
|
||||||
self, build_quilted_terrain_sprites, CurrentWorldSpriteState, FixtureTilePrefab,
|
|
||||||
FloorTilePrefab, TerrainSpriteState, TextureIDs, Textures,
|
|
||||||
};
|
|
||||||
use crate::{camera, game};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_platform::collections::hash_map::HashMap;
|
|
||||||
use bevy_platform::collections::HashSet;
|
|
||||||
use bevy_rand::prelude::*;
|
|
||||||
use noise::{NoiseFn, Perlin};
|
|
||||||
use rand::{Rng, SeedableRng};
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
pub const CHUNK_SIZE: i32 = 8;
|
|
||||||
|
|
||||||
pub const Z_BELOW: f32 = 45.0;
|
|
||||||
pub const Z_ABOVE: f32 = 15.0;
|
|
||||||
pub const Z_TOTAL: f32 = Z_ABOVE + Z_BELOW;
|
|
||||||
|
|
||||||
const _: () = assert!(Z_TOTAL <= 255.0);
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct ChunkMap {
|
|
||||||
pub loaded_chunks: HashMap<IVec2, (bool, i32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ChunkMap {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
loaded_chunks: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Event)]
|
|
||||||
pub struct GenerateChunkEvent {
|
|
||||||
pub chunk_position: IVec2,
|
|
||||||
}
|
|
||||||
#[derive(Event)]
|
|
||||||
pub struct ChunkTerrainEvent {
|
|
||||||
pub chunk_position: IVec2,
|
|
||||||
}
|
|
||||||
#[derive(Event)]
|
|
||||||
// CAlculate the weather effecrs and rivers/lakes of this chunk
|
|
||||||
pub struct ChunkWeatheringAndPrecipitationEvent {
|
|
||||||
pub chunk_position: IVec2,
|
|
||||||
}
|
|
||||||
#[derive(Event)]
|
|
||||||
pub struct ChunkForrestryEvent {
|
|
||||||
pub chunk_position: IVec2,
|
|
||||||
pub floor_tiles: Vec<(Vec3, String)>,
|
|
||||||
}
|
|
||||||
#[derive(Event)]
|
|
||||||
pub struct ChunkFoliageEvent {
|
|
||||||
pub chunk_position: IVec2,
|
|
||||||
}
|
|
||||||
#[derive(Event)]
|
|
||||||
pub struct ChunkFaunaEvent {
|
|
||||||
pub chunk_position: IVec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_chunk_system(mut commands: Commands) {
|
|
||||||
commands.insert_resource(TileMap::default());
|
|
||||||
commands.insert_resource(ChunkMap::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Event)]
|
|
||||||
pub struct TileOcclusionEvent {
|
|
||||||
pub tile_position: IVec3,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_tile_occlusion_updates(
|
|
||||||
mut tilemap: ResMut<TileMap>,
|
|
||||||
mut floor_tiles: Query<(Entity, &mut FloorTile, &Transform)>,
|
|
||||||
mut cwss: ResMut<CurrentWorldSpriteState>,
|
|
||||||
mut events: EventReader<TileOcclusionEvent>,
|
|
||||||
) {
|
|
||||||
let start = Instant::now();
|
|
||||||
|
|
||||||
let count = events.len();
|
|
||||||
|
|
||||||
// Process events in parallel
|
|
||||||
let updates: Vec<(IVec3, [u32; 8])> = events
|
|
||||||
.par_read()
|
|
||||||
.into_iter()
|
|
||||||
.map(|event| {
|
|
||||||
let pos = event.0.tile_position;
|
|
||||||
let visibility = calculate_visibility(pos, &tilemap);
|
|
||||||
(pos, visibility)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Map updates for efficient lookup
|
|
||||||
let update_map: HashMap<IVec3, [u32; 8]> = updates.into_iter().collect();
|
|
||||||
|
|
||||||
// Update only the relevant FloorTile components
|
|
||||||
for (_, mut tile, pos) in floor_tiles.iter_mut() {
|
|
||||||
if let Some(visibility) = update_map.get(&pos.translation.as_ivec3()) {
|
|
||||||
tile.visible_range = *visibility;
|
|
||||||
if let Some(tile_data) = tilemap.floor_tiles.get_mut(&pos.translation.as_ivec3()) {
|
|
||||||
tile_data.5 = *visibility;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
cwss.state = TerrainSpriteState::WaitingForRender;
|
|
||||||
println!(
|
|
||||||
"Tile occlusion calculated for {} tiles in {:.2?}",
|
|
||||||
count,
|
|
||||||
start.elapsed()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
|
||||||
let mut visible_range = [0u32; 8];
|
|
||||||
|
|
||||||
for mut camera_z in -Z_BELOW as i32..=Z_ABOVE as i32 {
|
|
||||||
camera_z *= ITILE_SIZE;
|
|
||||||
let mut is_visible = false;
|
|
||||||
|
|
||||||
if pos.z > camera_z {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut is_occluded = false;
|
|
||||||
|
|
||||||
let v_check_height: i32 = Z_TOTAL as i32 - (camera_z / ITILE_SIZE) + Z_BELOW as i32 + 1;
|
|
||||||
'vertical_check: for z_offset in 1..v_check_height {
|
|
||||||
let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * ITILE_SIZE));
|
|
||||||
if above_pos.z <= camera_z {
|
|
||||||
if let Some(&(_, _, _, visibly_transparent, _, _)) =
|
|
||||||
tilemap.floor_tiles.get(&above_pos)
|
|
||||||
{
|
|
||||||
if !visibly_transparent {
|
|
||||||
is_occluded = true;
|
|
||||||
break 'vertical_check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break 'vertical_check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !is_occluded {
|
|
||||||
'neighbor_check: for x_offset in -1..=1 {
|
|
||||||
for y_offset in -1..=1 {
|
|
||||||
for z_offset in 0..=1 {
|
|
||||||
if x_offset == 0 && y_offset == 0 && z_offset == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let neighbor_pos = IVec3::new(
|
|
||||||
pos.x + x_offset * ITILE_SIZE,
|
|
||||||
pos.y + y_offset * ITILE_SIZE,
|
|
||||||
pos.z + z_offset * ITILE_SIZE,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(&(id, _, _, _, _, _)) = tilemap.floor_tiles.get(&neighbor_pos) {
|
|
||||||
if id == 0 {
|
|
||||||
// id 0 = air tile
|
|
||||||
is_visible = true;
|
|
||||||
break 'neighbor_check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_visible {
|
|
||||||
let z2 = ((camera_z / ITILE_SIZE) + Z_BELOW as i32) as usize;
|
|
||||||
visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
visible_range
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_surface_terrain(x: i32, y: i32) -> f32 {
|
|
||||||
let noise = Perlin::new(constants::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
|
|
||||||
}
|
|
||||||
|
|
||||||
fn chunkmap_despawn_timer_system(
|
|
||||||
mut chunk_map: ResMut<ChunkMap>,
|
|
||||||
mut cwss: ResMut<CurrentWorldSpriteState>,
|
|
||||||
) {
|
|
||||||
for (_, (is_loaded, timer)) in chunk_map.loaded_chunks.iter_mut() {
|
|
||||||
if !*is_loaded {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if *timer > 0 {
|
|
||||||
*timer -= 1;
|
|
||||||
} else {
|
|
||||||
//TODO - remove chunk from chunkmap
|
|
||||||
|
|
||||||
*is_loaded = false;
|
|
||||||
cwss.state = TerrainSpriteState::WaitingForRender;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_chunk_events(
|
|
||||||
mut chunk_events: EventReader<GenerateChunkEvent>,
|
|
||||||
mut chunk_map: ResMut<ChunkMap>,
|
|
||||||
mut terrain_event_writer: EventWriter<ChunkTerrainEvent>,
|
|
||||||
mut weathering_event_writer: EventWriter<ChunkWeatheringAndPrecipitationEvent>,
|
|
||||||
mut foliage_event_writer: EventWriter<ChunkFoliageEvent>,
|
|
||||||
mut fauna_event_writer: EventWriter<ChunkFaunaEvent>,
|
|
||||||
mut cwss: ResMut<CurrentWorldSpriteState>,
|
|
||||||
) {
|
|
||||||
let mut any = false;
|
|
||||||
for event in chunk_events.par_read() {
|
|
||||||
let chunk_pos = event.0.chunk_position;
|
|
||||||
if let Some((true, _)) = chunk_map.loaded_chunks.get(&chunk_pos) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
any = true;
|
|
||||||
let chunk_map_updates: Mutex<HashMap<IVec2, bool>> = Mutex::new(HashMap::new());
|
|
||||||
// Mark chunk as loaded in our thread-safe collection
|
|
||||||
chunk_map_updates.lock().unwrap().insert(chunk_pos, true);
|
|
||||||
terrain_event_writer.write(ChunkTerrainEvent {
|
|
||||||
chunk_position: chunk_pos,
|
|
||||||
});
|
|
||||||
weathering_event_writer.write(ChunkWeatheringAndPrecipitationEvent {
|
|
||||||
chunk_position: chunk_pos,
|
|
||||||
});
|
|
||||||
foliage_event_writer.write(ChunkFoliageEvent {
|
|
||||||
chunk_position: chunk_pos,
|
|
||||||
});
|
|
||||||
fauna_event_writer.write(ChunkFaunaEvent {
|
|
||||||
chunk_position: chunk_pos,
|
|
||||||
});
|
|
||||||
// Apply all collected updates to the actual resources
|
|
||||||
for (chunk_pos, value) in chunk_map_updates.into_inner().unwrap() {
|
|
||||||
chunk_map.loaded_chunks.insert(chunk_pos, (value, 1800));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if any {
|
|
||||||
cwss.state = TerrainSpriteState::WaitingForRender;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(constants::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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
constants::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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_chunk_foliage(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut events: EventReader<GenerateChunkEvent>,
|
|
||||||
mut chunk_map: ResMut<ChunkMap>,
|
|
||||||
mut tilemap: ResMut<TileMap>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_chunk_fauna(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut events: EventReader<GenerateChunkEvent>,
|
|
||||||
mut chunk_map: ResMut<ChunkMap>,
|
|
||||||
mut tilemap: ResMut<TileMap>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_initial_chunks(mut event_writer: EventWriter<GenerateChunkEvent>) {
|
|
||||||
for x in -5..=5 {
|
|
||||||
for y in -5..=5 {
|
|
||||||
event_writer.write(GenerateChunkEvent {
|
|
||||||
chunk_position: IVec2::new(x, y),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct VisibleGameEntity;
|
|
||||||
|
|
||||||
pub fn compute_visibility_of_game_entities(
|
|
||||||
mut query: Query<(&Transform, &mut Visibility, &mut Sprite), With<VisibleGameEntity>>,
|
|
||||||
z_index: Res<game::ZIndex>,
|
|
||||||
) {
|
|
||||||
query
|
|
||||||
.par_iter_mut()
|
|
||||||
.for_each(|(transform, mut visibility, mut sprite)| {
|
|
||||||
let entity_z = (transform.translation.z / TILE_SIZE) - 1.0;
|
|
||||||
// if same z-level, always visible
|
|
||||||
if z_index.0 == entity_z {
|
|
||||||
sprite.color = Color::WHITE;
|
|
||||||
sprite.color.set_alpha(1.0);
|
|
||||||
*visibility = Visibility::Visible;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// if entity too low, saturation does not matter
|
|
||||||
if z_index.0 - entity_z > 8.0 {
|
|
||||||
*visibility = Visibility::Hidden;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// if entity too high, saturation does not matter
|
|
||||||
if entity_z > z_index.0 {
|
|
||||||
*visibility = Visibility::Hidden;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if visible, calculate saturation
|
|
||||||
*visibility = Visibility::Visible;
|
|
||||||
let saturation =
|
|
||||||
((z_index.0 - (transform.translation.z / TILE_SIZE) + 1.) / 8.0).clamp(0.0, 1.0);
|
|
||||||
|
|
||||||
sprite.color = Color::hsv(194.7, saturation, 1.0 - (saturation / 2.0));
|
|
||||||
|
|
||||||
sprite.color.set_alpha(1.0 - saturation);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TilemapPlugin;
|
|
||||||
|
|
||||||
impl Plugin for TilemapPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.init_resource::<ChunkMap>()
|
|
||||||
.add_event::<GenerateChunkEvent>()
|
|
||||||
.add_event::<ChunkTerrainEvent>()
|
|
||||||
.add_event::<ChunkWeatheringAndPrecipitationEvent>()
|
|
||||||
.add_event::<ChunkForrestryEvent>()
|
|
||||||
.add_event::<ChunkFoliageEvent>()
|
|
||||||
.add_event::<ChunkFaunaEvent>()
|
|
||||||
.add_event::<TileOcclusionEvent>()
|
|
||||||
.add_systems(Startup, (setup_chunk_system, setup_initial_chunks).chain())
|
|
||||||
.add_systems(
|
|
||||||
FixedUpdate,
|
|
||||||
(
|
|
||||||
(
|
|
||||||
handle_chunk_events,
|
|
||||||
generate_chunk_terrain,
|
|
||||||
generate_chunk_weathering_and_precipitation,
|
|
||||||
generate_chunk_forrestry,
|
|
||||||
generate_chunk_foliage,
|
|
||||||
generate_chunk_fauna,
|
|
||||||
)
|
|
||||||
.chain(),
|
|
||||||
chunkmap_despawn_timer_system,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(
|
|
||||||
compute_visibility_of_game_entities,
|
|
||||||
(
|
|
||||||
handle_tile_occlusion_updates,
|
|
||||||
build_quilted_terrain_sprites,
|
|
||||||
update_tile_visibility.run_if(
|
|
||||||
|cwss: Res<tiles::CurrentWorldSpriteState>,
|
|
||||||
camera_moved: Res<camera::CameraMoved>| {
|
|
||||||
cwss.state == tiles::TerrainSpriteState::RenderReady
|
|
||||||
|| camera_moved.0
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.chain(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_platform::collections::HashMap;
|
||||||
|
use bevy_platform::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::world::{tiles::TileMap, CurrentWorldSpriteState, TerrainSpriteState};
|
||||||
|
|
||||||
|
pub const CHUNK_SIZE: i32 = 8;
|
||||||
|
|
||||||
|
pub const Z_BELOW: f32 = 45.0;
|
||||||
|
pub const Z_ABOVE: f32 = 15.0;
|
||||||
|
pub const Z_TOTAL: f32 = Z_ABOVE + Z_BELOW;
|
||||||
|
|
||||||
|
const _: () = assert!(Z_TOTAL <= 255.0);
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct ChunkMap {
|
||||||
|
pub loaded_chunks: HashMap<IVec2, (bool, i32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ChunkMap {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
loaded_chunks: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct GenerateChunkEvent {
|
||||||
|
pub chunk_position: IVec2,
|
||||||
|
}
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct ChunkTerrainEvent {
|
||||||
|
pub chunk_position: IVec2,
|
||||||
|
}
|
||||||
|
#[derive(Event)]
|
||||||
|
// CAlculate the weather effecrs and rivers/lakes of this chunk
|
||||||
|
pub struct ChunkWeatheringAndPrecipitationEvent {
|
||||||
|
pub chunk_position: IVec2,
|
||||||
|
}
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct ChunkForrestryEvent {
|
||||||
|
pub chunk_position: IVec2,
|
||||||
|
pub floor_tiles: Vec<(Vec3, String)>,
|
||||||
|
}
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct ChunkFoliageEvent {
|
||||||
|
pub chunk_position: IVec2,
|
||||||
|
}
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct ChunkFaunaEvent {
|
||||||
|
pub chunk_position: IVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_chunk_system(mut commands: Commands) {
|
||||||
|
commands.insert_resource(TileMap::default());
|
||||||
|
commands.insert_resource(ChunkMap::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_chunk_events(
|
||||||
|
mut chunk_events: EventReader<GenerateChunkEvent>,
|
||||||
|
mut chunk_map: ResMut<ChunkMap>,
|
||||||
|
mut terrain_event_writer: EventWriter<ChunkTerrainEvent>,
|
||||||
|
mut weathering_event_writer: EventWriter<ChunkWeatheringAndPrecipitationEvent>,
|
||||||
|
mut foliage_event_writer: EventWriter<ChunkFoliageEvent>,
|
||||||
|
mut fauna_event_writer: EventWriter<ChunkFaunaEvent>,
|
||||||
|
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||||
|
) {
|
||||||
|
let mut any = false;
|
||||||
|
for event in chunk_events.par_read() {
|
||||||
|
let chunk_pos = event.0.chunk_position;
|
||||||
|
if let Some((true, _)) = chunk_map.loaded_chunks.get(&chunk_pos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
any = true;
|
||||||
|
let chunk_map_updates: Mutex<HashMap<IVec2, bool>> = Mutex::new(HashMap::new());
|
||||||
|
// Mark chunk as loaded in our thread-safe collection
|
||||||
|
chunk_map_updates.lock().unwrap().insert(chunk_pos, true);
|
||||||
|
terrain_event_writer.write(ChunkTerrainEvent {
|
||||||
|
chunk_position: chunk_pos,
|
||||||
|
});
|
||||||
|
weathering_event_writer.write(ChunkWeatheringAndPrecipitationEvent {
|
||||||
|
chunk_position: chunk_pos,
|
||||||
|
});
|
||||||
|
foliage_event_writer.write(ChunkFoliageEvent {
|
||||||
|
chunk_position: chunk_pos,
|
||||||
|
});
|
||||||
|
fauna_event_writer.write(ChunkFaunaEvent {
|
||||||
|
chunk_position: chunk_pos,
|
||||||
|
});
|
||||||
|
// Apply all collected updates to the actual resources
|
||||||
|
for (chunk_pos, value) in chunk_map_updates.into_inner().unwrap() {
|
||||||
|
chunk_map.loaded_chunks.insert(chunk_pos, (value, 1800));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if any {
|
||||||
|
cwss.state = TerrainSpriteState::WaitingForRender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chunkmap_despawn_timer_system(
|
||||||
|
mut chunk_map: ResMut<ChunkMap>,
|
||||||
|
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||||
|
) {
|
||||||
|
for (_, (is_loaded, timer)) in chunk_map.loaded_chunks.iter_mut() {
|
||||||
|
if !*is_loaded {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if *timer > 0 {
|
||||||
|
*timer -= 1;
|
||||||
|
} else {
|
||||||
|
//TODO - remove chunk from chunkmap
|
||||||
|
|
||||||
|
*is_loaded = false;
|
||||||
|
cwss.state = TerrainSpriteState::WaitingForRender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod management;
|
||||||
|
|
||||||
|
pub use management::*;
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
use crate::{
|
||||||
|
camera,
|
||||||
|
world::generation::{
|
||||||
|
generate_chunk_fauna, generate_chunk_foliage, generate_chunk_forrestry,
|
||||||
|
generate_chunk_terrain, generate_chunk_weathering_and_precipitation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub mod chunks;
|
||||||
|
pub mod generation;
|
||||||
|
pub mod textures;
|
||||||
|
pub mod tiles;
|
||||||
|
|
||||||
|
// Re-export commonly used items
|
||||||
|
pub use chunks::management::*;
|
||||||
|
pub use textures::management::*;
|
||||||
|
pub use tiles::{prefabs::*, rendering::*, visibility::*};
|
||||||
|
|
||||||
|
// Plugin
|
||||||
|
pub struct WorldPlugin;
|
||||||
|
|
||||||
|
impl Plugin for WorldPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.init_resource::<ChunkMap>()
|
||||||
|
.add_event::<GenerateChunkEvent>()
|
||||||
|
.add_event::<ChunkTerrainEvent>()
|
||||||
|
.add_event::<ChunkWeatheringAndPrecipitationEvent>()
|
||||||
|
.add_event::<ChunkForrestryEvent>()
|
||||||
|
.add_event::<ChunkFoliageEvent>()
|
||||||
|
.add_event::<ChunkFaunaEvent>()
|
||||||
|
.add_event::<TileOcclusionEvent>()
|
||||||
|
.add_systems(Startup, (setup_chunk_system, setup_initial_chunks).chain())
|
||||||
|
.add_systems(
|
||||||
|
FixedUpdate,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
handle_chunk_events,
|
||||||
|
generate_chunk_terrain,
|
||||||
|
generate_chunk_weathering_and_precipitation,
|
||||||
|
generate_chunk_forrestry,
|
||||||
|
generate_chunk_foliage,
|
||||||
|
generate_chunk_fauna,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
chunkmap_despawn_timer_system,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
compute_visibility_of_game_entities,
|
||||||
|
(
|
||||||
|
handle_tile_occlusion_updates,
|
||||||
|
build_quilted_terrain_sprites,
|
||||||
|
update_tile_visibility.run_if(
|
||||||
|
|cwss: Res<tiles::CurrentWorldSpriteState>,
|
||||||
|
camera_moved: Res<camera::CameraMoved>| {
|
||||||
|
cwss.state == tiles::TerrainSpriteState::RenderReady
|
||||||
|
|| camera_moved.0
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_initial_chunks(mut event_writer: EventWriter<GenerateChunkEvent>) {
|
||||||
|
for x in -5..=5 {
|
||||||
|
for y in -5..=5 {
|
||||||
|
event_writer.write(GenerateChunkEvent {
|
||||||
|
chunk_position: IVec2::new(x, y),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_platform::collections::HashMap;
|
||||||
|
use bevy_platform::time::Instant;
|
||||||
|
|
||||||
|
pub const FLOOR_ID_OFFSET: u32 = 0;
|
||||||
|
pub const FIXTURE_ID_OFFSET: u32 = 500000;
|
||||||
|
|
||||||
|
pub const DEFAULT_TEXTURE: &str = "default.png";
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct Textures {
|
||||||
|
pub handles: HashMap<String, Handle<Image>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct TextureIDs {
|
||||||
|
pub refs: HashMap<u32, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const GRASS_FLOOR_PATH: &str = "grass_floor.png";
|
||||||
|
const DIRT_FLOOR_PATH: &str = "dirt_floor.png";
|
||||||
|
const ROCK_FLOOR_PATH: &str = "rock_floor.png";
|
||||||
|
const BEDROCK_FLOOR_PATH: &str = "bedrock_floor.png";
|
||||||
|
const SKY_PATH: &str = "sky.png";
|
||||||
|
const DIRT_WALL_PATH: &str = "dirt_wall.png";
|
||||||
|
const ROCK_WALL_PATH: &str = "rock_wall.png";
|
||||||
|
const BEDROCK_WALL_PATH: &str = "bedrock_wall.png";
|
||||||
|
const LOG_PATH: &str = "log.png";
|
||||||
|
const LEAVES_PATH: &str = "leaves.png";
|
||||||
|
|
||||||
|
pub fn initialize_textures(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
println!("");
|
||||||
|
println!("####################");
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut textures: HashMap<String, Handle<Image>> = HashMap::new();
|
||||||
|
let mut texture_ids: HashMap<u32, String> = HashMap::new();
|
||||||
|
texture_ids.insert(u32::MAX, DEFAULT_TEXTURE.to_string());
|
||||||
|
|
||||||
|
texture_ids.insert(FLOOR_ID_OFFSET, SKY_PATH.to_string());
|
||||||
|
textures.insert(SKY_PATH.to_string(), asset_server.load(SKY_PATH));
|
||||||
|
texture_ids.insert(FLOOR_ID_OFFSET + 1, GRASS_FLOOR_PATH.to_string());
|
||||||
|
textures.insert(
|
||||||
|
GRASS_FLOOR_PATH.to_string(),
|
||||||
|
asset_server.load(GRASS_FLOOR_PATH),
|
||||||
|
);
|
||||||
|
texture_ids.insert(FLOOR_ID_OFFSET + 2, DIRT_FLOOR_PATH.to_string());
|
||||||
|
textures.insert(
|
||||||
|
DIRT_FLOOR_PATH.to_string(),
|
||||||
|
asset_server.load(DIRT_FLOOR_PATH),
|
||||||
|
);
|
||||||
|
texture_ids.insert(FLOOR_ID_OFFSET + 3, ROCK_FLOOR_PATH.to_string());
|
||||||
|
textures.insert(
|
||||||
|
ROCK_FLOOR_PATH.to_string(),
|
||||||
|
asset_server.load(ROCK_FLOOR_PATH),
|
||||||
|
);
|
||||||
|
texture_ids.insert(FLOOR_ID_OFFSET + 4, BEDROCK_FLOOR_PATH.to_string());
|
||||||
|
textures.insert(
|
||||||
|
BEDROCK_FLOOR_PATH.to_string(),
|
||||||
|
asset_server.load(BEDROCK_FLOOR_PATH),
|
||||||
|
);
|
||||||
|
|
||||||
|
texture_ids.insert(FIXTURE_ID_OFFSET + 1, DIRT_WALL_PATH.to_string());
|
||||||
|
textures.insert(
|
||||||
|
DIRT_WALL_PATH.to_string(),
|
||||||
|
asset_server.load(DIRT_WALL_PATH),
|
||||||
|
);
|
||||||
|
texture_ids.insert(FIXTURE_ID_OFFSET + 2, ROCK_WALL_PATH.to_string());
|
||||||
|
textures.insert(
|
||||||
|
ROCK_WALL_PATH.to_string(),
|
||||||
|
asset_server.load(ROCK_WALL_PATH),
|
||||||
|
);
|
||||||
|
texture_ids.insert(FIXTURE_ID_OFFSET + 3, BEDROCK_WALL_PATH.to_string());
|
||||||
|
textures.insert(
|
||||||
|
BEDROCK_WALL_PATH.to_string(),
|
||||||
|
asset_server.load(BEDROCK_WALL_PATH),
|
||||||
|
);
|
||||||
|
texture_ids.insert(FIXTURE_ID_OFFSET + 4, LOG_PATH.to_string());
|
||||||
|
textures.insert(LOG_PATH.to_string(), asset_server.load(LOG_PATH));
|
||||||
|
texture_ids.insert(FIXTURE_ID_OFFSET + 5, LEAVES_PATH.to_string());
|
||||||
|
textures.insert(LEAVES_PATH.to_string(), asset_server.load(LEAVES_PATH));
|
||||||
|
|
||||||
|
commands.insert_resource(Textures { handles: textures });
|
||||||
|
commands.insert_resource(TextureIDs { refs: texture_ids });
|
||||||
|
println!("Textures initialized in {:.2?}", start.elapsed());
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod management;
|
||||||
|
|
||||||
|
pub use management::*;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Clone)]
|
||||||
|
pub struct FloorTile {
|
||||||
|
pub id: u32,
|
||||||
|
pub opaque: bool,
|
||||||
|
pub walkable: bool,
|
||||||
|
pub astar_weight: u8,
|
||||||
|
pub visible_range: [u32; 8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FloorTile {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: 0,
|
||||||
|
opaque: true,
|
||||||
|
walkable: true,
|
||||||
|
astar_weight: 0,
|
||||||
|
visible_range: [0; 8],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Clone)]
|
||||||
|
pub struct FixtureTile {
|
||||||
|
pub id: u32,
|
||||||
|
pub solid: bool,
|
||||||
|
pub visible_range: [u32; 8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FixtureTile {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: 0,
|
||||||
|
solid: true,
|
||||||
|
visible_range: [0; 8],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct TileState {
|
||||||
|
pub timer: Timer,
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
pub mod components;
|
||||||
|
pub mod prefabs;
|
||||||
|
pub mod rendering;
|
||||||
|
pub mod tilemap;
|
||||||
|
pub mod visibility;
|
||||||
|
|
||||||
|
pub use components::*;
|
||||||
|
pub use prefabs::*;
|
||||||
|
pub use rendering::*;
|
||||||
|
pub use tilemap::*;
|
||||||
|
pub use visibility::*;
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::world::{
|
||||||
|
tiles::{FixtureTile, FloorTile, TileState},
|
||||||
|
FIXTURE_ID_OFFSET,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Bundle)]
|
||||||
|
pub struct FloorTilePrefab {
|
||||||
|
transform: Transform,
|
||||||
|
tile: FloorTile,
|
||||||
|
tile_state: TileState,
|
||||||
|
visibility: Visibility,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloorTilePrefab {
|
||||||
|
pub fn grass(position: Vec3) -> Self {
|
||||||
|
FloorTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FloorTile {
|
||||||
|
id: 1,
|
||||||
|
astar_weight: 100,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tile_state: TileState {
|
||||||
|
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dirt(position: Vec3) -> Self {
|
||||||
|
FloorTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FloorTile {
|
||||||
|
id: 2,
|
||||||
|
astar_weight: 85,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tile_state: TileState {
|
||||||
|
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rock(position: Vec3) -> Self {
|
||||||
|
FloorTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FloorTile {
|
||||||
|
id: 3,
|
||||||
|
astar_weight: 50,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tile_state: TileState {
|
||||||
|
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn air(position: Vec3) -> Self {
|
||||||
|
FloorTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FloorTile {
|
||||||
|
id: 0,
|
||||||
|
opaque: false,
|
||||||
|
walkable: false,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tile_state: TileState {
|
||||||
|
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bedrock(position: Vec3) -> Self {
|
||||||
|
FloorTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FloorTile {
|
||||||
|
id: 4,
|
||||||
|
astar_weight: 150,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tile_state: TileState {
|
||||||
|
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn(self, commands: &mut Commands) {
|
||||||
|
commands.spawn((self.tile, self.transform, self.tile_state, self.visibility));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Bundle)]
|
||||||
|
pub struct FixtureTilePrefab {
|
||||||
|
transform: Transform,
|
||||||
|
tile: FixtureTile,
|
||||||
|
visibility: Visibility,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixtureTilePrefab {
|
||||||
|
pub fn dirt_wall(position: Vec3) -> Self {
|
||||||
|
FixtureTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FixtureTile {
|
||||||
|
id: FIXTURE_ID_OFFSET + 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rock_wall(position: Vec3) -> Self {
|
||||||
|
FixtureTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FixtureTile {
|
||||||
|
id: FIXTURE_ID_OFFSET + 2,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log(position: Vec3) -> Self {
|
||||||
|
FixtureTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FixtureTile {
|
||||||
|
id: FIXTURE_ID_OFFSET + 4,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn leaves(position: Vec3) -> Self {
|
||||||
|
FixtureTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FixtureTile {
|
||||||
|
id: FIXTURE_ID_OFFSET + 5,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bedrock_wall(position: Vec3) -> Self {
|
||||||
|
FixtureTilePrefab {
|
||||||
|
transform: Transform::from_translation(position),
|
||||||
|
tile: FixtureTile {
|
||||||
|
id: 0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
visibility: Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn(self, commands: &mut Commands) -> Entity {
|
||||||
|
commands
|
||||||
|
.spawn((self.tile, self.transform, self.visibility))
|
||||||
|
.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,93 +1,13 @@
|
|||||||
use crate::tile::{FixtureTile, FloorTile, TileState};
|
use bevy::{asset::RenderAssetUsages, prelude::*, render::render_resource};
|
||||||
use crate::tilemap::Z_BELOW;
|
use bevy_platform::collections::HashMap;
|
||||||
use crate::{constants::*, tilemap};
|
use bevy_platform::sync::Mutex;
|
||||||
use bevy::asset::RenderAssetUsages;
|
use bevy_platform::time::Instant;
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy::render::render_resource;
|
|
||||||
use bevy_platform::collections::hash_map::HashMap;
|
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
#[derive(Resource)]
|
use crate::{
|
||||||
pub struct Textures {
|
constants::{PIXEL_RATIO, TILE_PIXELS, TILE_SIZE},
|
||||||
pub handles: HashMap<String, Handle<Image>>,
|
world::{tiles::FloorTile, TextureIDs, Textures, Z_BELOW, Z_TOTAL},
|
||||||
}
|
};
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct TextureIDs {
|
|
||||||
pub refs: HashMap<u32, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const FLOOR_ID_OFFSET: u32 = 0;
|
|
||||||
const FIXTURE_ID_OFFSET: u32 = 500000;
|
|
||||||
|
|
||||||
pub const DEFAULT_TEXTURE: &str = "default.png";
|
|
||||||
|
|
||||||
const GRASS_FLOOR_PATH: &str = "grass_floor.png";
|
|
||||||
const DIRT_FLOOR_PATH: &str = "dirt_floor.png";
|
|
||||||
const ROCK_FLOOR_PATH: &str = "rock_floor.png";
|
|
||||||
const BEDROCK_FLOOR_PATH: &str = "bedrock_floor.png";
|
|
||||||
const SKY_PATH: &str = "sky.png";
|
|
||||||
const DIRT_WALL_PATH: &str = "dirt_wall.png";
|
|
||||||
const ROCK_WALL_PATH: &str = "rock_wall.png";
|
|
||||||
const BEDROCK_WALL_PATH: &str = "bedrock_wall.png";
|
|
||||||
const LOG_PATH: &str = "log.png";
|
|
||||||
const LEAVES_PATH: &str = "leaves.png";
|
|
||||||
|
|
||||||
pub fn initialize_textures(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
||||||
println!("");
|
|
||||||
println!("####################");
|
|
||||||
let start = Instant::now();
|
|
||||||
let mut textures: HashMap<String, Handle<Image>> = HashMap::new();
|
|
||||||
let mut texture_ids: HashMap<u32, String> = HashMap::new();
|
|
||||||
texture_ids.insert(u32::MAX, DEFAULT_TEXTURE.to_string());
|
|
||||||
|
|
||||||
texture_ids.insert(FLOOR_ID_OFFSET, SKY_PATH.to_string());
|
|
||||||
textures.insert(SKY_PATH.to_string(), asset_server.load(SKY_PATH));
|
|
||||||
texture_ids.insert(FLOOR_ID_OFFSET + 1, GRASS_FLOOR_PATH.to_string());
|
|
||||||
textures.insert(
|
|
||||||
GRASS_FLOOR_PATH.to_string(),
|
|
||||||
asset_server.load(GRASS_FLOOR_PATH),
|
|
||||||
);
|
|
||||||
texture_ids.insert(FLOOR_ID_OFFSET + 2, DIRT_FLOOR_PATH.to_string());
|
|
||||||
textures.insert(
|
|
||||||
DIRT_FLOOR_PATH.to_string(),
|
|
||||||
asset_server.load(DIRT_FLOOR_PATH),
|
|
||||||
);
|
|
||||||
texture_ids.insert(FLOOR_ID_OFFSET + 3, ROCK_FLOOR_PATH.to_string());
|
|
||||||
textures.insert(
|
|
||||||
ROCK_FLOOR_PATH.to_string(),
|
|
||||||
asset_server.load(ROCK_FLOOR_PATH),
|
|
||||||
);
|
|
||||||
texture_ids.insert(FLOOR_ID_OFFSET + 4, BEDROCK_FLOOR_PATH.to_string());
|
|
||||||
textures.insert(
|
|
||||||
BEDROCK_FLOOR_PATH.to_string(),
|
|
||||||
asset_server.load(BEDROCK_FLOOR_PATH),
|
|
||||||
);
|
|
||||||
|
|
||||||
texture_ids.insert(FIXTURE_ID_OFFSET + 1, DIRT_WALL_PATH.to_string());
|
|
||||||
textures.insert(
|
|
||||||
DIRT_WALL_PATH.to_string(),
|
|
||||||
asset_server.load(DIRT_WALL_PATH),
|
|
||||||
);
|
|
||||||
texture_ids.insert(FIXTURE_ID_OFFSET + 2, ROCK_WALL_PATH.to_string());
|
|
||||||
textures.insert(
|
|
||||||
ROCK_WALL_PATH.to_string(),
|
|
||||||
asset_server.load(ROCK_WALL_PATH),
|
|
||||||
);
|
|
||||||
texture_ids.insert(FIXTURE_ID_OFFSET + 3, BEDROCK_WALL_PATH.to_string());
|
|
||||||
textures.insert(
|
|
||||||
BEDROCK_WALL_PATH.to_string(),
|
|
||||||
asset_server.load(BEDROCK_WALL_PATH),
|
|
||||||
);
|
|
||||||
texture_ids.insert(FIXTURE_ID_OFFSET + 4, LOG_PATH.to_string());
|
|
||||||
textures.insert(LOG_PATH.to_string(), asset_server.load(LOG_PATH));
|
|
||||||
texture_ids.insert(FIXTURE_ID_OFFSET + 5, LEAVES_PATH.to_string());
|
|
||||||
textures.insert(LEAVES_PATH.to_string(), asset_server.load(LEAVES_PATH));
|
|
||||||
|
|
||||||
commands.insert_resource(Textures { handles: textures });
|
|
||||||
commands.insert_resource(TextureIDs { refs: texture_ids });
|
|
||||||
println!("Textures initialized in {:.2?}", start.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum TerrainSpriteState {
|
pub enum TerrainSpriteState {
|
||||||
@@ -102,10 +22,6 @@ pub struct CurrentWorldSpriteState {
|
|||||||
pub state: TerrainSpriteState,
|
pub state: TerrainSpriteState,
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct TerrainSprite {
|
pub struct TerrainSprite {
|
||||||
pub z_index: usize, // Store the z-index this sprite belongs to (as world_Z)
|
pub z_index: usize, // Store the z-index this sprite belongs to (as world_Z)
|
||||||
@@ -158,7 +74,7 @@ pub fn build_quilted_terrain_sprites(
|
|||||||
for (floortile, transform) in query_tiles.iter() {
|
for (floortile, transform) in query_tiles.iter() {
|
||||||
let position = Vec2::new(transform.translation.x, transform.translation.y);
|
let position = Vec2::new(transform.translation.x, transform.translation.y);
|
||||||
|
|
||||||
for z_index in 0..=tilemap::Z_TOTAL as usize {
|
for z_index in 0..=Z_TOTAL as usize {
|
||||||
if (floortile.visible_range[z_index / 32] & (1 << (z_index % 32) as u32)) != 0 {
|
if (floortile.visible_range[z_index / 32] & (1 << (z_index % 32) as u32)) != 0 {
|
||||||
tiles_by_z
|
tiles_by_z
|
||||||
.entry(z_index)
|
.entry(z_index)
|
||||||
@@ -384,185 +300,3 @@ fn blit_texture_with_alpha(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This system handles updating quilted sprites when the world changes
|
|
||||||
// pub fn update_quilts_on_world_change(
|
|
||||||
// mut commands: Commands,
|
|
||||||
// mut quilt_cache: ResMut<QuiltCache>,
|
|
||||||
// mut cwss: ResMut<CurrentWorldSpriteState>,
|
|
||||||
// Add any resources or queries that indicate world changes
|
|
||||||
// For example, if you have a WorldChangeEvent:
|
|
||||||
// mut world_changes: EventReader<WorldChangeEvent>,
|
|
||||||
// ) {
|
|
||||||
// Example: Check for world changes
|
|
||||||
// if !world_changes.is_empty() {
|
|
||||||
// for event in world_changes.iter() {
|
|
||||||
// quilt_cache.dirty_indices.push(event.z_index);
|
|
||||||
// }
|
|
||||||
// cwss.state = WorldSpriteState::WaitingForRender;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Alternatively, if you have specific systems that modify the world,
|
|
||||||
// you could have them set cwss.state = WorldSpriteState::WaitingForRender
|
|
||||||
// and add affected z-indices to quilt_cache.dirty_indices
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Bundle)]
|
|
||||||
pub struct FloorTilePrefab {
|
|
||||||
transform: Transform,
|
|
||||||
tile: FloorTile,
|
|
||||||
tile_state: TileState,
|
|
||||||
visibility: Visibility,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FloorTilePrefab {
|
|
||||||
pub fn grass(position: Vec3) -> Self {
|
|
||||||
FloorTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FloorTile {
|
|
||||||
id: 1,
|
|
||||||
astar_weight: 100,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
tile_state: TileState {
|
|
||||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dirt(position: Vec3) -> Self {
|
|
||||||
FloorTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FloorTile {
|
|
||||||
id: 2,
|
|
||||||
astar_weight: 85,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
tile_state: TileState {
|
|
||||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rock(position: Vec3) -> Self {
|
|
||||||
FloorTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FloorTile {
|
|
||||||
id: 3,
|
|
||||||
astar_weight: 50,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
tile_state: TileState {
|
|
||||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn air(position: Vec3) -> Self {
|
|
||||||
FloorTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FloorTile {
|
|
||||||
id: 0,
|
|
||||||
opaque: false,
|
|
||||||
walkable: false,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
tile_state: TileState {
|
|
||||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bedrock(position: Vec3) -> Self {
|
|
||||||
FloorTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FloorTile {
|
|
||||||
id: 4,
|
|
||||||
astar_weight: 150,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
tile_state: TileState {
|
|
||||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn(self, commands: &mut Commands) {
|
|
||||||
commands.spawn((self.tile, self.transform, self.tile_state, self.visibility));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Bundle)]
|
|
||||||
pub struct FixtureTilePrefab {
|
|
||||||
transform: Transform,
|
|
||||||
tile: FixtureTile,
|
|
||||||
visibility: Visibility,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixtureTilePrefab {
|
|
||||||
pub fn dirt_wall(position: Vec3) -> Self {
|
|
||||||
FixtureTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FixtureTile {
|
|
||||||
id: FIXTURE_ID_OFFSET + 1,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rock_wall(position: Vec3) -> Self {
|
|
||||||
FixtureTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FixtureTile {
|
|
||||||
id: FIXTURE_ID_OFFSET + 2,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log(position: Vec3) -> Self {
|
|
||||||
FixtureTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FixtureTile {
|
|
||||||
id: FIXTURE_ID_OFFSET + 4,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn leaves(position: Vec3) -> Self {
|
|
||||||
FixtureTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FixtureTile {
|
|
||||||
id: FIXTURE_ID_OFFSET + 5,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bedrock_wall(position: Vec3) -> Self {
|
|
||||||
FixtureTilePrefab {
|
|
||||||
transform: Transform::from_translation(position),
|
|
||||||
tile: FixtureTile {
|
|
||||||
id: 0,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
visibility: Visibility::Hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn(self, commands: &mut Commands) -> Entity {
|
|
||||||
commands
|
|
||||||
.spawn((self.tile, self.transform, self.visibility))
|
|
||||||
.id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_platform::collections::hash_map::HashMap;
|
||||||
|
|
||||||
|
#[derive(Resource, Default, Clone)]
|
||||||
|
pub struct TileMap {
|
||||||
|
pub floor_tiles: HashMap<IVec3, (i32, bool, bool, bool, i32, [u32; 8])>, //id, canStandIn, canStandOn, visiblyTransparent, astar_weight, visible_range
|
||||||
|
pub fixture_tiles: HashMap<IVec3, (i32, bool, bool, [u32; 8])>, // id, canStandIn, canStandOn, visible_range
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
use crate::{
|
||||||
|
constants::{ITILE_SIZE, TILE_SIZE},
|
||||||
|
game,
|
||||||
|
world::{
|
||||||
|
chunks::{Z_ABOVE, Z_BELOW, Z_TOTAL},
|
||||||
|
tiles::{CurrentWorldSpriteState, FloorTile, TerrainSprite, TerrainSpriteState, TileMap},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_platform::collections::HashMap;
|
||||||
|
use bevy_platform::time::Instant;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct VisibleGameEntity;
|
||||||
|
|
||||||
|
pub fn compute_visibility_of_game_entities(
|
||||||
|
mut query: Query<(&Transform, &mut Visibility, &mut Sprite), With<VisibleGameEntity>>,
|
||||||
|
z_index: Res<game::ZIndex>,
|
||||||
|
) {
|
||||||
|
query
|
||||||
|
.par_iter_mut()
|
||||||
|
.for_each(|(transform, mut visibility, mut sprite)| {
|
||||||
|
let entity_z = (transform.translation.z / TILE_SIZE) - 1.0;
|
||||||
|
// if same z-level, always visible
|
||||||
|
if z_index.0 == entity_z {
|
||||||
|
sprite.color = Color::WHITE;
|
||||||
|
sprite.color.set_alpha(1.0);
|
||||||
|
*visibility = Visibility::Visible;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// if entity too low, saturation does not matter
|
||||||
|
if z_index.0 - entity_z > 8.0 {
|
||||||
|
*visibility = Visibility::Hidden;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if entity too high, saturation does not matter
|
||||||
|
if entity_z > z_index.0 {
|
||||||
|
*visibility = Visibility::Hidden;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if visible, calculate saturation
|
||||||
|
*visibility = Visibility::Visible;
|
||||||
|
let saturation =
|
||||||
|
((z_index.0 - (transform.translation.z / TILE_SIZE) + 1.) / 8.0).clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
sprite.color = Color::hsv(194.7, saturation, 1.0 - (saturation / 2.0));
|
||||||
|
|
||||||
|
sprite.color.set_alpha(1.0 - saturation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct TileOcclusionEvent {
|
||||||
|
pub tile_position: IVec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_tile_occlusion_updates(
|
||||||
|
mut tilemap: ResMut<TileMap>,
|
||||||
|
mut floor_tiles: Query<(Entity, &mut FloorTile, &Transform)>,
|
||||||
|
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||||
|
mut events: EventReader<TileOcclusionEvent>,
|
||||||
|
) {
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
let count = events.len();
|
||||||
|
|
||||||
|
// Process events in parallel
|
||||||
|
let updates: Vec<(IVec3, [u32; 8])> = events
|
||||||
|
.par_read()
|
||||||
|
.into_iter()
|
||||||
|
.map(|event| {
|
||||||
|
let pos = event.0.tile_position;
|
||||||
|
let visibility = calculate_visibility(pos, &tilemap);
|
||||||
|
(pos, visibility)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Map updates for efficient lookup
|
||||||
|
let update_map: HashMap<IVec3, [u32; 8]> = updates.into_iter().collect();
|
||||||
|
|
||||||
|
// Update only the relevant FloorTile components
|
||||||
|
for (_, mut tile, pos) in floor_tiles.iter_mut() {
|
||||||
|
if let Some(visibility) = update_map.get(&pos.translation.as_ivec3()) {
|
||||||
|
tile.visible_range = *visibility;
|
||||||
|
if let Some(tile_data) = tilemap.floor_tiles.get_mut(&pos.translation.as_ivec3()) {
|
||||||
|
tile_data.5 = *visibility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
cwss.state = TerrainSpriteState::WaitingForRender;
|
||||||
|
println!(
|
||||||
|
"Tile occlusion calculated for {} tiles in {:.2?}",
|
||||||
|
count,
|
||||||
|
start.elapsed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
||||||
|
let mut visible_range = [0u32; 8];
|
||||||
|
|
||||||
|
for mut camera_z in -Z_BELOW as i32..=Z_ABOVE as i32 {
|
||||||
|
camera_z *= ITILE_SIZE;
|
||||||
|
let mut is_visible = false;
|
||||||
|
|
||||||
|
if pos.z > camera_z {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut is_occluded = false;
|
||||||
|
|
||||||
|
let v_check_height: i32 = Z_TOTAL as i32 - (camera_z / ITILE_SIZE) + Z_BELOW as i32 + 1;
|
||||||
|
'vertical_check: for z_offset in 1..v_check_height {
|
||||||
|
let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * ITILE_SIZE));
|
||||||
|
if above_pos.z <= camera_z {
|
||||||
|
if let Some(&(_, _, _, visibly_transparent, _, _)) =
|
||||||
|
tilemap.floor_tiles.get(&above_pos)
|
||||||
|
{
|
||||||
|
if !visibly_transparent {
|
||||||
|
is_occluded = true;
|
||||||
|
break 'vertical_check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break 'vertical_check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !is_occluded {
|
||||||
|
'neighbor_check: for x_offset in -1..=1 {
|
||||||
|
for y_offset in -1..=1 {
|
||||||
|
for z_offset in 0..=1 {
|
||||||
|
if x_offset == 0 && y_offset == 0 && z_offset == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let neighbor_pos = IVec3::new(
|
||||||
|
pos.x + x_offset * ITILE_SIZE,
|
||||||
|
pos.y + y_offset * ITILE_SIZE,
|
||||||
|
pos.z + z_offset * ITILE_SIZE,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(&(id, _, _, _, _, _)) = tilemap.floor_tiles.get(&neighbor_pos) {
|
||||||
|
if id == 0 {
|
||||||
|
// id 0 = air tile
|
||||||
|
is_visible = true;
|
||||||
|
break 'neighbor_check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_visible {
|
||||||
|
let z2 = ((camera_z / ITILE_SIZE) + Z_BELOW as i32) as usize;
|
||||||
|
visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visible_range
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_tile_visibility(
|
||||||
|
z_index: Res<game::ZIndex>,
|
||||||
|
mut query: Query<(&TerrainSprite, &mut Visibility)>,
|
||||||
|
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||||
|
) {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
for (terrain_sprite, mut visibility) in query.iter_mut() {
|
||||||
|
*visibility = if terrain_sprite.z_index == ((z_index.0 + Z_BELOW) as usize) {
|
||||||
|
Visibility::Visible
|
||||||
|
} else {
|
||||||
|
Visibility::Hidden
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cwss.state = TerrainSpriteState::Inactive;
|
||||||
|
println!("Visibility update: {:.2?}", now.elapsed());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user