diff --git a/src/citizen.rs b/src/citizen.rs index 7ca1462..d47d724 100644 --- a/src/citizen.rs +++ b/src/citizen.rs @@ -64,9 +64,9 @@ pub fn spawn_citizens(mut commands: Commands, asset_server: Res) { // Spawn a handful of citizens for _ in 0..10 { - let x = rng.gen_range(-10.0..10.0); + let x = rng.gen_range(-15.0..10.0); let y = rng.gen_range(-10.0..10.0); - let position = Vec3::new(x, y, 0.0) * PIXEL_RATIO; + let position = Vec3::new(x, y, 0.1) * TILE_SIZE; commands.spawn(Citizen::new(&asset_server, position)); } diff --git a/src/constants.rs b/src/constants.rs index 51c6b13..8f68a1b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,2 +1,2 @@ -pub const PIXEL_RATIO: f32 = 1.; +pub const PIXEL_RATIO: f32 = 2.; pub const TILE_SIZE: f32 = 16.0 * PIXEL_RATIO; diff --git a/src/main.rs b/src/main.rs index e77ab91..703c7fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ - use bevy::prelude::*; mod camera; @@ -27,7 +26,6 @@ fn main() { ) .insert_resource(ClearColor(Color::srgb(0.0, 0.0, 0.0))) .add_plugins(tilemap::TilemapPlugin) - .add_plugins(tilemap::OcclusionPlugin) .add_systems( Startup, ( @@ -61,14 +59,14 @@ fn main() { fn setup_initial_chunks(mut event_writer: EventWriter) { println!("setup_initial_chunks"); - // Spawn a 3x3 grid of chunks around the origin - for x in -1..=1 { - for y in -1..=1 { - event_writer.send(tilemap::LoadChunkEvent { - chunk_position: IVec2::new(x, y), - }); - } - } + // // Spawn a 3x3 grid of chunks around the origin + // for x in -5..=5 { + // for y in -5..=5 { + // event_writer.send(tilemap::LoadChunkEvent { + // chunk_position: IVec2::new(x, y), + // }); + // } + // } } // New system to check camera position and load nearby chunks @@ -87,8 +85,8 @@ fn check_camera_position_for_chunks( (camera_pos.y / (tilemap::CHUNK_SIZE as f32 * constants::TILE_SIZE)).floor() as i32; // Check a 3x3 area around the camera's current chunk - for x in (chunk_x - 1)..=(chunk_x + 1) { - for y in (chunk_y - 1)..=(chunk_y + 1) { + for x in (chunk_x - 2)..=(chunk_x + 2) { + for y in (chunk_y - 2)..=(chunk_y + 2) { let chunk_pos = IVec2::new(x, y); // Only spawn new chunks if they haven't been loaded yet diff --git a/src/tile.rs b/src/tile.rs index c7376f5..b02723a 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -34,6 +34,11 @@ pub struct FixtureTile { pub visible_range: [u32; 8], } +#[derive(Component)] +pub struct NeedsOccluded { + pub has_been_occluded: bool, +} + impl Default for FixtureTile { fn default() -> Self { Self { @@ -68,7 +73,7 @@ pub fn camera_z_movement( } } -#[derive(Resource, Default)] +#[derive(Resource, Default, Clone)] pub struct TileMap { pub floors: HashMap, pub fixtures: HashMap, @@ -77,11 +82,10 @@ pub struct TileMap { pub fn update_tile_visibility( z_index: ResMut, mut query_set: ParamSet<( - Query<(&FloorTile, &mut Visibility)>, - Query<(&FixtureTile, &mut Visibility)>, + Query<(&FloorTile, &mut Visibility), Without>, + Query<(&FixtureTile, &mut Visibility), Without>, )>, ) { - let z_index = (z_index.0 as i32 + 150) as usize; // Update floor visibility diff --git a/src/tilemap.rs b/src/tilemap.rs index 6d003c5..c9f7518 100644 --- a/src/tilemap.rs +++ b/src/tilemap.rs @@ -1,5 +1,5 @@ use crate::constants::TILE_SIZE; -use crate::tile::{FixtureTile, FloorTile, TileMap}; +use crate::tile::{self, FixtureTile, FloorTile, NeedsOccluded, TileMap}; use crate::tiles::{FixtureTilePrefab, FloorTilePrefab}; use bevy::prelude::*; use noise::{NoiseFn, Perlin}; @@ -30,139 +30,131 @@ fn setup_chunk_system(mut commands: Commands) { commands.insert_resource(ChunkMap::default()); } -// Events -#[derive(Event)] -pub struct ChunkOcclusionEvent { - pub chunk_pos: IVec2, -} - -#[derive(Event)] -pub struct TileOcclusionUpdateEvent { - pub positions: Vec, -} - - // System to handle individual tile occlusion updates -fn handle_tile_occlusion_updates( - mut events: EventReader, +pub fn handle_tile_occlusion_updates( + mut commands: Commands, tilemap: Res, mut query_set: ParamSet<( - Query<(&mut FloorTile, &Transform)>, - Query<(&mut FixtureTile, &Transform)>, + Query<(&mut FloorTile, &Transform, &mut NeedsOccluded)>, + Query<(&mut FixtureTile, &Transform, &mut NeedsOccluded)>, + Query<(Entity, &mut NeedsOccluded)>, )>, ) { - let tile_size = TILE_SIZE as i32; - - let calculate_visibility = |pos: IVec3, tilemap: &TileMap| { - let mut visible_range = [0u32; 8]; - - for mut camera_z in -150..100 { - camera_z *= tile_size; - let mut is_visible = false; - - if pos.z > camera_z { - continue; + query_set + .p0() + .par_iter_mut() + .for_each(|(mut tile, transform, mut needs_occluded)| { + if needs_occluded.has_been_occluded { + return; } - - let mut is_occluded = false; - - 'vertical_check: for z_offset in 1..=35 { - let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * tile_size)); - if above_pos.z <= camera_z { - // Check both floors and fixtures for occlusion - if let Some(&(_, opaque, _, _, _)) = tilemap.floors.get(&above_pos) { - if opaque { - is_occluded = true; - break 'vertical_check; - } - } - if let Some(&(_, solid, _)) = tilemap.fixtures.get(&above_pos) { - if solid { - is_occluded = true; - break 'vertical_check; - } - } - } else { - break 'vertical_check; - } + 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; } - 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 * tile_size, - pos.y + y_offset * tile_size, - pos.z + z_offset * tile_size, - ); + fixture.visible_range = calculate_visibility( + transform.translation.as_ivec3() - IVec3::new(0, 0, 1), + &tilemap, + ); + needs_occluded.has_been_occluded = true; + }); - // Check both floors and fixtures for visibility - if let Some(&(id, _, _, _, _)) = tilemap.floors.get(&neighbor_pos) { - if id == 0 { - is_visible = true; - break 'neighbor_check; - } - } - if let Some(&(id, _, _)) = tilemap.fixtures.get(&neighbor_pos) { - if id == 0 { - is_visible = true; - break 'neighbor_check; - } - } - } - } - } - } - - if is_visible { - print!("#"); - let z2 = ((camera_z / tile_size) + 150) as usize; - visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32); - } - } - - visible_range - }; - - for event in events.read() { - print!("event2"); - // Process floor tiles - for (mut tile, transform) in query_set.p0().iter_mut() { - let pos = transform.translation.as_ivec3(); - if event.positions.contains(&pos) { - // this never calls - print!("floor"); - tile.visible_range = calculate_visibility(pos, &tilemap); - } - } - - // Process fixture tiles - for (mut fixture, transform) in query_set.p1().iter_mut() { - let pos = transform.translation.as_ivec3(); - if event.positions.contains(&pos) { - // this never calls - print!("fixture"); - let adjusted_pos = pos - IVec3::new(0, 0, 1); - fixture.visible_range = calculate_visibility(adjusted_pos, &tilemap); - } + for (entity, occ) in query_set.p2().iter_mut() { + if occ.has_been_occluded { + commands.entity(entity).remove::(); } } } +pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] { + let tile_size = TILE_SIZE as i32; + + let mut visible_range = [0u32; 8]; + + for mut camera_z in -150..100 { + camera_z *= tile_size; + let mut is_visible = false; + + if pos.z > camera_z { + continue; + } + + let mut is_occluded = false; + + 'vertical_check: for z_offset in 1..=35 { + let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * tile_size)); + if above_pos.z <= camera_z { + if let Some(&(_, opaque, _, _, _)) = tilemap.floors.get(&above_pos) { + if opaque { + is_occluded = true; + break 'vertical_check; + } + } + // if let Some(&(_, solid, _)) = tilemap.fixtures.get(&above_pos) { + // if solid { + // 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 * tile_size, + pos.y + y_offset * tile_size, + pos.z + z_offset * tile_size, + ); + + if let Some(&(id, _, _, _, _)) = tilemap.floors.get(&neighbor_pos) { + if id == 0 { + is_visible = true; + break 'neighbor_check; + } + } + // if let Some(&(id, _, _)) = tilemap.fixtures.get(&neighbor_pos) { + // if id == 0 { + // is_visible = true; + // break 'neighbor_check; + // } + // } + } + } + } + } + + if is_visible { + let z2 = ((camera_z / tile_size) + 150) as usize; + visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32); + } + } + + visible_range +} + fn handle_chunk_loading( mut commands: Commands, asset_server: Res, mut events: EventReader, mut chunk_map: ResMut, mut tilemap: ResMut, - mut occlusion_events: EventWriter, ) { let noise = Perlin::new(0); for event in events.read() { + println!("Loading chunk {}", event.chunk_position); let chunk_pos = event.chunk_position; if chunk_map.loaded_chunks.contains_key(&chunk_pos) { continue; @@ -181,8 +173,10 @@ fn handle_chunk_loading( let world_x = start_x + local_x; let world_y = start_y + local_y; - let noise_value_a = noise.get([world_x as f64 * 0.01, world_y as f64 * 0.01]) * 1.25; - let noise_value_b = noise.get([world_x as f64 * 0.05, world_y as f64 * 0.05]) * 0.25; + let noise_value_a = + noise.get([world_x as f64 * 0.01, world_y as f64 * 0.01]) * 1.25; + let noise_value_b = + noise.get([world_x as f64 * 0.05, world_y as f64 * 0.05]) * 0.25; let combined_noise_value = noise_value_a + noise_value_b; let noise_position = Vec3::new( @@ -201,33 +195,39 @@ fn handle_chunk_loading( let pos_ivec = position.as_ivec3(); if noise_position.z > position.z { - if z < -15 { - let noise_value = noise.get([world_x as f64 * 0.05, world_y as f64 * 0.05, z as f64 * 0.05]); + if z < -8 { + let noise_value = noise.get([ + world_x as f64 * 0.05, + world_y as f64 * 0.05, + z as f64 * 0.05, + ]); if noise_value < -0.5 { - commands.spawn(FloorTilePrefab::air(position, &asset_server)); + FloorTilePrefab::air(position, &asset_server).spawn(&mut commands); tilemap.floors.insert(pos_ivec, (0, false, true, 1, [0; 8])); // Air tile floor_positions.push((position, "air")); } else if noise_value < 0.8 { - commands.spawn(FloorTilePrefab::rock(position, &asset_server)); - tilemap.floors.insert(pos_ivec, (2, true, false, 255, [0; 8])); // Rock tile + FloorTilePrefab::rock(position, &asset_server).spawn(&mut commands); + tilemap + .floors + .insert(pos_ivec, (2, true, false, 255, [0; 8])); // Rock tile floor_positions.push((position, "rock")); } else { - commands.spawn(FloorTilePrefab::dirt(position, &asset_server)); + FloorTilePrefab::dirt(position, &asset_server).spawn(&mut commands); tilemap.floors.insert(pos_ivec, (1, true, true, 1, [0; 8])); // Dirt tile floor_positions.push((position, "dirt")); } } else { - commands.spawn(FloorTilePrefab::dirt(position, &asset_server)); + FloorTilePrefab::dirt(position, &asset_server).spawn(&mut commands); tilemap.floors.insert(pos_ivec, (1, true, true, 1, [0; 8])); // Dirt tile floor_positions.push((position, "dirt")); } } else if noise_position.z < position.z { - commands.spawn(FloorTilePrefab::air(position, &asset_server)); + FloorTilePrefab::air(position, &asset_server).spawn(&mut commands); tilemap.floors.insert(pos_ivec, (0, false, true, 1, [0; 8])); // Air tile floor_positions.push((position, "air")); } else { - commands.spawn(FloorTilePrefab::dirt(position, &asset_server)); - tilemap.floors.insert(pos_ivec, (1, true, true, 1, [0; 8])); // Dirt tile + FloorTilePrefab::grass(position, &asset_server).spawn(&mut commands); + tilemap.floors.insert(pos_ivec, (1, true, true, 1, [0; 8])); // Grass tile floor_positions.push((position, "dirt")); } } @@ -241,62 +241,20 @@ fn handle_chunk_loading( match *floor_type { "dirt" => { - commands.spawn(FixtureTilePrefab::dirt_wall(above_pos, &asset_server)); + FixtureTilePrefab::dirt_wall(above_pos, &asset_server).spawn(&mut commands); tilemap.fixtures.insert(above_ivec, (1, true, [0; 8])); // Dirt wall } "rock" => { - commands.spawn(FixtureTilePrefab::rock_wall(above_pos, &asset_server)); + FixtureTilePrefab::rock_wall(above_pos, &asset_server).spawn(&mut commands); tilemap.fixtures.insert(above_ivec, (2, true, [0; 8])); // Rock wall } "bedrock" => { - commands.spawn(FixtureTilePrefab::bedrock_wall(above_pos, &asset_server)); + FixtureTilePrefab::bedrock_wall(above_pos, &asset_server).spawn(&mut commands); tilemap.fixtures.insert(above_ivec, (3, true, [0; 8])); // Bedrock wall } _ => {} } } - - // Emit occlusion event for this chunk - occlusion_events.send(ChunkOcclusionEvent { - chunk_pos, - }); - } -} - - - -// System to handle chunk occlusion events -fn handle_chunk_occlusion_events( - mut events: EventReader, - tilemap: Res, - mut tile_update_events: EventWriter, -) { - for event in events.read() { - let chunk_pos = event.chunk_pos; - let tile_size = TILE_SIZE as i32; - let start_x = chunk_pos.x * CHUNK_SIZE * tile_size; - let end_x = start_x + (CHUNK_SIZE * tile_size); - let start_y = chunk_pos.y * CHUNK_SIZE * tile_size; - let end_y = start_y + (CHUNK_SIZE * tile_size); - - // Collect positions that need visibility updates - let mut positions = Vec::new(); - - // Add positions from the current chunk - for x in (start_x..end_x).step_by(tile_size as usize) { - for y in (start_y..end_y).step_by(tile_size as usize) { - for z in -150..50 { - let pos = IVec3::new(y, x, z * tile_size); - if tilemap.floors.contains_key(&pos) || tilemap.fixtures.contains_key(&pos) { - positions.push(pos); - } - } - } - } - // Send update event if we have positions to process - if !positions.is_empty() { - tile_update_events.send(TileOcclusionUpdateEvent { positions }); - } } } @@ -307,21 +265,9 @@ impl Plugin for TilemapPlugin { app.init_resource::() .add_event::() .add_systems(Startup, setup_chunk_system) - .add_systems(Update, handle_chunk_loading); + .add_systems( + FixedUpdate, + (handle_chunk_loading, handle_tile_occlusion_updates).chain(), + ); } } - -// Plugin to handle occlusion systems -pub struct OcclusionPlugin; - - -impl Plugin for OcclusionPlugin { - fn build(&self, app: &mut App) { - app.add_event::() - .add_event::() - .add_systems(Update, ( - handle_chunk_occlusion_events, - handle_tile_occlusion_updates, - ).chain()); - } -} \ No newline at end of file diff --git a/src/tiles.rs b/src/tiles.rs index 7f4bb31..803a292 100644 --- a/src/tiles.rs +++ b/src/tiles.rs @@ -1,5 +1,5 @@ use crate::constants::*; -use crate::tile::{FixtureTile, FloorTile, TileState}; +use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileState}; use bevy::prelude::*; #[derive(Bundle)] @@ -8,15 +8,16 @@ pub struct FloorTilePrefab { sprite: Sprite, tile: FloorTile, tile_state: TileState, - visibility: Visibility + visibility: Visibility, + needs_occluded: NeedsOccluded, } impl FloorTilePrefab { - pub fn dirt(position: Vec3, asset_server: &Res) -> Self { + pub fn grass(position: Vec3, asset_server: &Res) -> Self { FloorTilePrefab { transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)), sprite: Sprite { - image: asset_server.load("dirt_floor.png"), + image: asset_server.load("grass_floor.png"), ..Default::default() }, tile: FloorTile { @@ -27,6 +28,30 @@ impl FloorTilePrefab { timer: Timer::from_seconds(1.0, TimerMode::Repeating), }, visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, + } + } + + pub fn dirt(position: Vec3, asset_server: &Res) -> Self { + FloorTilePrefab { + transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)), + sprite: Sprite { + image: asset_server.load("dirt_floor.png"), + ..Default::default() + }, + tile: FloorTile { + id: 2, + ..Default::default() + }, + tile_state: TileState { + timer: Timer::from_seconds(1.0, TimerMode::Repeating), + }, + visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, } } @@ -45,6 +70,9 @@ impl FloorTilePrefab { timer: Timer::from_seconds(1.0, TimerMode::Repeating), }, visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, } } @@ -64,6 +92,9 @@ impl FloorTilePrefab { timer: Timer::from_seconds(1.0, TimerMode::Repeating), }, visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, } } @@ -82,8 +113,22 @@ impl FloorTilePrefab { timer: Timer::from_seconds(1.0, TimerMode::Repeating), }, visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, } } + + pub fn spawn(self, commands: &mut Commands) { + commands.spawn(( + self.tile, + self.transform, + self.sprite, + self.tile_state, + self.visibility, + self.needs_occluded, + )); + } } #[derive(Bundle)] @@ -91,7 +136,8 @@ pub struct FixtureTilePrefab { transform: Transform, sprite: Sprite, tile: FixtureTile, - visibility: Visibility + visibility: Visibility, + needs_occluded: NeedsOccluded, } impl FixtureTilePrefab { @@ -107,6 +153,9 @@ impl FixtureTilePrefab { ..Default::default() }, visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, } } @@ -122,6 +171,9 @@ impl FixtureTilePrefab { ..Default::default() }, visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, } } @@ -137,6 +189,19 @@ impl FixtureTilePrefab { ..Default::default() }, visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, } } + + pub fn spawn(self, commands: &mut Commands) { + commands.spawn(( + self.tile, + self.transform, + self.sprite, + self.visibility, + self.needs_occluded, + )); + } }