rework occlusion events

This commit is contained in:
2025-05-23 14:01:28 +01:00
parent a245f35332
commit ac033de136
5 changed files with 107 additions and 128 deletions
+5 -6
View File
@@ -17,7 +17,7 @@ impl Citizen {
pub fn new(asset_server: &Res<AssetServer>, position: Vec3) -> Self { pub fn new(asset_server: &Res<AssetServer>, position: Vec3) -> Self {
Citizen { Citizen {
ambulatory: Ambulatory { ambulatory: Ambulatory {
walk_speed: 8., walk_speed: 0.,
run_speed: 6., run_speed: 6.,
target: None, target: None,
current_path: None, current_path: None,
@@ -140,10 +140,10 @@ pub fn citizen_movement(
&mut Sprite, &mut Sprite,
)>, )>,
tilemap: Res<TileMap>, tilemap: Res<TileMap>,
z_index: Res<game::ZIndex>,
) { ) {
query.par_iter_mut().for_each( query
|(mut ambulatory, mut transform, mut visibility, mut sprite)| { .par_iter_mut()
.for_each(|(mut ambulatory, mut transform, _, _)| {
let current_pos = transform.translation; let current_pos = transform.translation;
let below_pos = Vec3::new( let below_pos = Vec3::new(
current_pos.x, current_pos.x,
@@ -200,8 +200,7 @@ pub fn citizen_movement(
} }
} }
} }
}, });
);
} }
fn is_standable_tile(tilemap: &TileMap, pos: IVec3) -> bool { fn is_standable_tile(tilemap: &TileMap, pos: IVec3) -> bool {
+2 -7
View File
@@ -16,6 +16,7 @@ fn main() {
.insert_resource(tiles::CurrentWorldSpriteState { .insert_resource(tiles::CurrentWorldSpriteState {
state: tiles::TerrainSpriteState::Inactive, state: tiles::TerrainSpriteState::Inactive,
}) })
.insert_resource(camera::CameraMoved(true))
.insert_resource(tiles::QuiltCache::default()) .insert_resource(tiles::QuiltCache::default())
.add_systems(PreStartup, tiles::initialize_textures) .add_systems(PreStartup, tiles::initialize_textures)
.add_plugins( .add_plugins(
@@ -40,19 +41,13 @@ fn main() {
citizen::spawn_citizens, citizen::spawn_citizens,
), ),
) )
.add_systems(
FixedUpdate,
(tiles::build_quilted_terrain_sprites, camera::scroll_events),
)
.init_resource::<camera::CameraMoved>()
.add_systems( .add_systems(
Update, Update,
( (
camera::scroll_events,
camera::camera_z_movement, camera::camera_z_movement,
camera::camera_movement, camera::camera_movement,
cursor::move_cursor, cursor::move_cursor,
tile::update_tile_visibility
.run_if(|camera_moved: Res<camera::CameraMoved>| camera_moved.0),
), ),
) )
.run(); .run();
+6 -6
View File
@@ -1,6 +1,9 @@
use std::time::Instant; use std::time::Instant;
use crate::{game, tilemap, tiles}; use crate::{
game, tilemap,
tiles::{self, TerrainSpriteState},
};
use bevy::prelude::*; use bevy::prelude::*;
use bevy_platform::collections::hash_map::HashMap; use bevy_platform::collections::hash_map::HashMap;
@@ -32,11 +35,6 @@ pub struct FixtureTile {
pub visible_range: [u32; 8], pub visible_range: [u32; 8],
} }
#[derive(Component)]
pub struct NeedsOccluded {
pub has_been_occluded: bool,
}
impl Default for FixtureTile { impl Default for FixtureTile {
fn default() -> Self { fn default() -> Self {
Self { Self {
@@ -56,6 +54,7 @@ pub struct TileMap {
pub fn update_tile_visibility( pub fn update_tile_visibility(
z_index: Res<game::ZIndex>, z_index: Res<game::ZIndex>,
mut query: Query<(&tiles::TerrainSprite, &mut Visibility)>, mut query: Query<(&tiles::TerrainSprite, &mut Visibility)>,
mut cwss: ResMut<tiles::CurrentWorldSpriteState>,
) { ) {
let now = Instant::now(); let now = Instant::now();
@@ -69,6 +68,7 @@ pub fn update_tile_visibility(
Visibility::Hidden Visibility::Hidden
}; };
} }
cwss.state = TerrainSpriteState::Inactive;
println!("Visibility update: {:.2?}", now.elapsed()); println!("Visibility update: {:.2?}", now.elapsed());
} }
+87 -61
View File
@@ -2,20 +2,20 @@ use std::sync::Mutex;
use std::time::Instant; use std::time::Instant;
use crate::constants::{ITILE_SIZE, TILE_SIZE}; use crate::constants::{ITILE_SIZE, TILE_SIZE};
use crate::game; use crate::tile::{update_tile_visibility, FloorTile, TileMap};
use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileMap};
use crate::tiles::{ use crate::tiles::{
CurrentWorldSpriteState, FixtureTilePrefab, FloorTilePrefab, TerrainSpriteState, TextureIDs, self, build_quilted_terrain_sprites, CurrentWorldSpriteState, FixtureTilePrefab,
Textures, FloorTilePrefab, TerrainSpriteState, TextureIDs, Textures,
}; };
use crate::{camera, game};
use bevy::prelude::*; use bevy::prelude::*;
use bevy_platform::collections::hash_map::HashMap; use bevy_platform::collections::hash_map::HashMap;
use noise::{NoiseFn, Perlin}; use noise::{NoiseFn, Perlin};
pub const CHUNK_SIZE: i32 = 8; pub const CHUNK_SIZE: i32 = 8;
pub const Z_BELOW: f32 = 6.0; pub const Z_BELOW: f32 = 10.0;
pub const Z_ABOVE: f32 = 6.0; pub const Z_ABOVE: f32 = 8.0;
pub const Z_TOTAL: f32 = Z_ABOVE + Z_BELOW; // MAX 255 DO NOT EXCEED pub const Z_TOTAL: f32 = Z_ABOVE + Z_BELOW; // MAX 255 DO NOT EXCEED
pub const SEED: u32 = 420; pub const SEED: u32 = 420;
@@ -65,48 +65,52 @@ fn setup_chunk_system(mut commands: Commands) {
commands.insert_resource(ChunkMap::default()); commands.insert_resource(ChunkMap::default());
} }
#[derive(Event)]
pub struct TileOcclusionEvent {
pub tile_position: IVec3,
}
pub fn handle_tile_occlusion_updates( pub fn handle_tile_occlusion_updates(
mut commands: Commands, mut tilemap: ResMut<TileMap>,
tilemap: Res<TileMap>, mut floor_tiles: Query<(Entity, &mut FloorTile, &Transform)>,
mut query_set: ParamSet<(
Query<(&mut FloorTile, &Transform, &mut NeedsOccluded)>,
Query<(&mut FixtureTile, &Transform, &mut NeedsOccluded)>,
Query<(Entity, &mut NeedsOccluded)>,
)>,
mut cwss: ResMut<CurrentWorldSpriteState>, mut cwss: ResMut<CurrentWorldSpriteState>,
mut events: EventReader<TileOcclusionEvent>,
) { ) {
let start = Instant::now(); let start = Instant::now();
query_set
.p0()
.par_iter_mut()
.for_each(|(mut tile, transform, mut needs_occluded)| {
if needs_occluded.has_been_occluded {
return;
}
tile.visible_range = calculate_visibility(transform.translation.as_ivec3(), &tilemap);
needs_occluded.has_been_occluded = true;
});
query_set
.p1()
.par_iter_mut()
.for_each(|(mut fixture, transform, mut needs_occluded)| {
if needs_occluded.has_been_occluded {
return;
}
fixture.visible_range = calculate_visibility(
transform.translation.as_ivec3() - IVec3::new(0, 0, 1),
&tilemap,
);
needs_occluded.has_been_occluded = true;
});
for (entity, occ) in query_set.p2().iter_mut() { let count = events.len();
if occ.has_been_occluded {
commands.entity(entity).remove::<NeedsOccluded>(); // 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.4 = *visibility;
}
} }
} }
cwss.state = TerrainSpriteState::WaitingForRender; if count > 0 {
println!("Tile occlusion updated in {:.2?}", start.elapsed()); cwss.state = TerrainSpriteState::WaitingForRender;
println!(
"Tile occlusion updated {} tiles in {:.2?}",
count,
start.elapsed()
);
}
} }
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] { pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
@@ -189,7 +193,9 @@ fn generate_chunks_from_algo(
mut weathering_event_writer: EventWriter<ChunkWeatheringAndPrecipitationEvent>, mut weathering_event_writer: EventWriter<ChunkWeatheringAndPrecipitationEvent>,
mut foliage_event_writer: EventWriter<ChunkFoliageEvent>, mut foliage_event_writer: EventWriter<ChunkFoliageEvent>,
mut fauna_event_writer: EventWriter<ChunkFaunaEvent>, mut fauna_event_writer: EventWriter<ChunkFaunaEvent>,
mut cwss: ResMut<CurrentWorldSpriteState>,
) { ) {
let count = chunk_events.len();
// Fire each terrain pass. They will all fire sequentially. // Fire each terrain pass. They will all fire sequentially.
for event in chunk_events.read() { for event in chunk_events.read() {
let chunk_pos = event.chunk_position; let chunk_pos = event.chunk_position;
@@ -206,6 +212,9 @@ fn generate_chunks_from_algo(
chunk_position: chunk_pos, chunk_position: chunk_pos,
}); });
} }
if count > 0 {
cwss.state = TerrainSpriteState::WaitingForRender;
}
} }
fn generate_chunk_terrain( fn generate_chunk_terrain(
@@ -214,6 +223,7 @@ fn generate_chunk_terrain(
mut chunk_map: ResMut<ChunkMap>, mut chunk_map: ResMut<ChunkMap>,
mut tilemap: ResMut<TileMap>, mut tilemap: ResMut<TileMap>,
mut forrestry_event_writer: EventWriter<ChunkForrestryEvent>, mut forrestry_event_writer: EventWriter<ChunkForrestryEvent>,
mut occlusion_event_writer: EventWriter<TileOcclusionEvent>,
) { ) {
let is_empty = events.is_empty(); let is_empty = events.is_empty();
let start = Instant::now(); let start = Instant::now();
@@ -341,6 +351,7 @@ fn generate_chunk_terrain(
for (pos, data) in tilemap_updates.into_inner().unwrap() { for (pos, data) in tilemap_updates.into_inner().unwrap() {
tilemap.floor_tiles.insert(pos, data); tilemap.floor_tiles.insert(pos, data);
occlusion_event_writer.write(TileOcclusionEvent { tile_position: pos });
} }
// Send all forrestry events // Send all forrestry events
@@ -434,11 +445,13 @@ fn generate_chunk_forrestry(
for (ivec, data) in collected_updates { for (ivec, data) in collected_updates {
tilemap.fixture_tiles.insert(ivec, data); tilemap.fixture_tiles.insert(ivec, data);
} }
println!( if count > 0 {
"Forrestry update for {:?} chunks in {:.2?}", println!(
count, "Forrestry update for {:?} chunks in {:.2?}",
start.elapsed() count,
); start.elapsed()
);
}
} }
fn generate_chunk_foliage( fn generate_chunk_foliage(
@@ -458,8 +471,8 @@ fn generate_chunk_fauna(
} }
fn setup_initial_chunks(mut event_writer: EventWriter<GenerateChunkEvent>) { fn setup_initial_chunks(mut event_writer: EventWriter<GenerateChunkEvent>) {
for x in -16..=16 { for x in -8..=8 {
for y in -9..=9 { for y in -5..=5 {
event_writer.write(GenerateChunkEvent { event_writer.write(GenerateChunkEvent {
chunk_position: IVec2::new(x, y), chunk_position: IVec2::new(x, y),
}); });
@@ -526,24 +539,37 @@ impl Plugin for TilemapPlugin {
.add_event::<ChunkForrestryEvent>() .add_event::<ChunkForrestryEvent>()
.add_event::<ChunkFoliageEvent>() .add_event::<ChunkFoliageEvent>()
.add_event::<ChunkFaunaEvent>() .add_event::<ChunkFaunaEvent>()
.add_systems(Startup, (setup_chunk_system, setup_initial_chunks)) .add_event::<TileOcclusionEvent>()
.add_systems(Startup, (setup_chunk_system, setup_initial_chunks).chain())
.add_systems( .add_systems(
PostStartup, FixedUpdate,
( (
generate_chunks_from_algo, generate_chunks_from_algo,
( generate_chunk_terrain,
generate_chunk_terrain, generate_chunk_weathering_and_precipitation,
generate_chunk_weathering_and_precipitation, generate_chunk_forrestry,
generate_chunk_forrestry, generate_chunk_foliage,
generate_chunk_foliage, generate_chunk_fauna,
generate_chunk_fauna,
)
.chain(),
handle_tile_occlusion_updates,
) )
.chain(), .chain(),
) )
.add_systems(FixedUpdate, generate_chunks_from_algo) .add_systems(
.add_systems(Update, compute_visibility_of_game_entities); 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(),
),
);
} }
} }
+7 -48
View File
@@ -1,5 +1,4 @@
use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileState}; use crate::tile::{FixtureTile, FloorTile, TileState};
use crate::tilemap::Z_BELOW;
use crate::{constants::*, tilemap}; use crate::{constants::*, tilemap};
use bevy::asset::RenderAssetUsages; use bevy::asset::RenderAssetUsages;
use bevy::prelude::*; use bevy::prelude::*;
@@ -120,12 +119,12 @@ impl Default for QuiltCache {
// here be dragons :( // here be dragons :(
pub fn build_quilted_terrain_sprites( pub fn build_quilted_terrain_sprites(
query: Query<(&FloorTile, &Transform)>, query_tiles: Query<(&FloorTile, &Transform)>,
commands: ParallelCommands<'_, '_>, commands: ParallelCommands<'_, '_>,
mut cwss: ResMut<CurrentWorldSpriteState>, mut cwss: ResMut<CurrentWorldSpriteState>,
textures: Res<Textures>, textures: Res<Textures>,
texture_ids: Res<TextureIDs>, texture_ids: Res<TextureIDs>,
query_sprites: Query<Entity, With<TerrainSprite>>, query_terrain_sprites: Query<Entity, With<TerrainSprite>>,
mut images: ResMut<Assets<Image>>, mut images: ResMut<Assets<Image>>,
mut quilt_cache: ResMut<QuiltCache>, mut quilt_cache: ResMut<QuiltCache>,
) { ) {
@@ -136,7 +135,7 @@ pub fn build_quilted_terrain_sprites(
cwss.state = TerrainSpriteState::InProgress; cwss.state = TerrainSpriteState::InProgress;
// Despawn existing terrain sprites // Despawn existing terrain sprites
let despawn_entities: Vec<Entity> = query_sprites.iter().collect(); let despawn_entities: Vec<Entity> = query_terrain_sprites.iter().collect();
for entity in despawn_entities { for entity in despawn_entities {
commands.command_scope(|mut cmd| { commands.command_scope(|mut cmd| {
cmd.entity(entity).despawn(); cmd.entity(entity).despawn();
@@ -147,7 +146,7 @@ pub fn build_quilted_terrain_sprites(
let mut tiles_by_z: HashMap<usize, Vec<(Vec2, &FloorTile)>> = HashMap::new(); let mut tiles_by_z: HashMap<usize, Vec<(Vec2, &FloorTile)>> = HashMap::new();
// Calculate bounds for all visible tiles // Calculate bounds for all visible tiles
for (floortile, transform) in query.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..=tilemap::Z_TOTAL as usize {
@@ -410,7 +409,6 @@ pub struct FloorTilePrefab {
tile: FloorTile, tile: FloorTile,
tile_state: TileState, tile_state: TileState,
visibility: Visibility, visibility: Visibility,
needs_occluded: NeedsOccluded,
} }
impl FloorTilePrefab { impl FloorTilePrefab {
@@ -426,9 +424,6 @@ impl FloorTilePrefab {
timer: Timer::from_seconds(1.0, TimerMode::Repeating), timer: Timer::from_seconds(1.0, TimerMode::Repeating),
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
@@ -444,9 +439,6 @@ impl FloorTilePrefab {
timer: Timer::from_seconds(1.0, TimerMode::Repeating), timer: Timer::from_seconds(1.0, TimerMode::Repeating),
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
@@ -462,9 +454,6 @@ impl FloorTilePrefab {
timer: Timer::from_seconds(1.0, TimerMode::Repeating), timer: Timer::from_seconds(1.0, TimerMode::Repeating),
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
@@ -481,9 +470,6 @@ impl FloorTilePrefab {
timer: Timer::from_seconds(1.0, TimerMode::Repeating), timer: Timer::from_seconds(1.0, TimerMode::Repeating),
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
@@ -499,20 +485,11 @@ impl FloorTilePrefab {
timer: Timer::from_seconds(1.0, TimerMode::Repeating), timer: Timer::from_seconds(1.0, TimerMode::Repeating),
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
pub fn spawn(self, commands: &mut Commands) { pub fn spawn(self, commands: &mut Commands) {
commands.spawn(( commands.spawn((self.tile, self.transform, self.tile_state, self.visibility));
self.tile,
self.transform,
self.tile_state,
self.visibility,
self.needs_occluded,
));
} }
} }
@@ -521,7 +498,6 @@ pub struct FixtureTilePrefab {
transform: Transform, transform: Transform,
tile: FixtureTile, tile: FixtureTile,
visibility: Visibility, visibility: Visibility,
needs_occluded: NeedsOccluded,
} }
impl FixtureTilePrefab { impl FixtureTilePrefab {
@@ -533,9 +509,6 @@ impl FixtureTilePrefab {
..Default::default() ..Default::default()
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
@@ -547,9 +520,6 @@ impl FixtureTilePrefab {
..Default::default() ..Default::default()
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
@@ -561,9 +531,6 @@ impl FixtureTilePrefab {
..Default::default() ..Default::default()
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
@@ -575,20 +542,12 @@ impl FixtureTilePrefab {
..Default::default() ..Default::default()
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
needs_occluded: NeedsOccluded {
has_been_occluded: false,
},
} }
} }
pub fn spawn(self, commands: &mut Commands) -> Entity { pub fn spawn(self, commands: &mut Commands) -> Entity {
commands commands
.spawn(( .spawn((self.tile, self.transform, self.visibility))
self.tile,
self.transform,
self.visibility,
self.needs_occluded,
))
.id() .id()
} }
} }